Commit 9ca8b170 authored by lepdou's avatar lepdou

abtest

parent f0b72c35
...@@ -25,16 +25,24 @@ public class ClusterController { ...@@ -25,16 +25,24 @@ public class ClusterController {
private ClusterService clusterService; private ClusterService clusterService;
@RequestMapping(path = "/apps/{appId}/clusters", method = RequestMethod.POST) @RequestMapping(path = "/apps/{appId}/clusters", method = RequestMethod.POST)
public ClusterDTO create(@PathVariable("appId") String appId, @RequestBody ClusterDTO dto) { public ClusterDTO create(@PathVariable("appId") String appId,
@RequestParam(value = "autoCreatePrivateNamespace", defaultValue = "true") boolean autoCreatePrivateNamespace,
@RequestBody ClusterDTO dto) {
if (!InputValidator.isValidClusterNamespace(dto.getName())) { if (!InputValidator.isValidClusterNamespace(dto.getName())) {
throw new BadRequestException(String.format("Cluster格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE)); throw new BadRequestException(String.format("Cluster格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
} }
Cluster entity = BeanUtils.transfrom(Cluster.class, dto); Cluster entity = BeanUtils.transfrom(Cluster.class, dto);
Cluster managedEntity = clusterService.findOne(appId, entity.getName()); Cluster managedEntity = clusterService.findOne(appId, entity.getName());
if (managedEntity != null) { if (managedEntity != null) {
throw new BadRequestException("cluster already exist."); throw new BadRequestException("cluster already exist.");
} }
entity = clusterService.save(entity);
if (autoCreatePrivateNamespace) {
entity = clusterService.saveWithCreatePrivateNamespace(entity);
} else {
entity = clusterService.saveWithoutCreatePrivateNamespace(entity);
}
dto = BeanUtils.transfrom(ClusterDTO.class, entity); dto = BeanUtils.transfrom(ClusterDTO.class, entity);
return dto; return dto;
...@@ -52,7 +60,7 @@ public class ClusterController { ...@@ -52,7 +60,7 @@ public class ClusterController {
@RequestMapping("/apps/{appId}/clusters") @RequestMapping("/apps/{appId}/clusters")
public List<ClusterDTO> find(@PathVariable("appId") String appId) { public List<ClusterDTO> find(@PathVariable("appId") String appId) {
List<Cluster> clusters = clusterService.findClusters(appId); List<Cluster> clusters = clusterService.findParentClusters(appId);
return BeanUtils.batchTransform(ClusterDTO.class, clusters); return BeanUtils.batchTransform(ClusterDTO.class, clusters);
} }
......
package com.ctrip.framework.apollo.adminservice.controller; package com.ctrip.framework.apollo.adminservice.controller;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
...@@ -159,12 +160,20 @@ public class InstanceConfigController { ...@@ -159,12 +160,20 @@ public class InstanceConfigController {
} }
@RequestMapping(value = "/by-namespace", method = RequestMethod.GET) @RequestMapping(value = "/by-namespace", method = RequestMethod.GET)
public PageDTO<InstanceDTO> getInstancesByNamespace(@RequestParam("appId") String appId, public PageDTO<InstanceDTO> getInstancesByNamespace(
@RequestParam("clusterName") String clusterName, @RequestParam("appId") String appId, @RequestParam("clusterName") String clusterName,
@RequestParam("namespaceName") String @RequestParam("namespaceName") String namespaceName,
namespaceName, Pageable pageable) { @RequestParam(value = "instanceAppId", required = false) String instanceAppId,
Page<Instance> instances = instanceService.findInstancesByNamespace(appId, clusterName, Pageable pageable) {
Page<Instance> instances;
if (Strings.isNullOrEmpty(instanceAppId)) {
instances = instanceService.findInstancesByNamespace(appId, clusterName,
namespaceName, pageable); namespaceName, pageable);
} else {
instances = instanceService.findInstancesByNamespaceAndInstanceAppId(instanceAppId, appId,
clusterName, namespaceName, pageable);
}
List<InstanceDTO> instanceDTOs = BeanUtils.batchTransform(InstanceDTO.class, instances.getContent()); List<InstanceDTO> instanceDTOs = BeanUtils.batchTransform(InstanceDTO.class, instances.getContent());
return new PageDTO<>(instanceDTOs, pageable, instances.getTotalElements()); return new PageDTO<>(instanceDTOs, pageable, instances.getTotalElements());
} }
......
...@@ -45,13 +45,6 @@ public class ItemController { ...@@ -45,13 +45,6 @@ public class ItemController {
if (managedEntity != null) { if (managedEntity != null) {
throw new BadRequestException("item already exist"); throw new BadRequestException("item already exist");
} else { } else {
Item lastItem = itemService.findLastOne(appId, clusterName, namespaceName);
int lineNum = 1;
if (lastItem != null) {
Integer lastItemNum = lastItem.getLineNum();
lineNum = lastItemNum == null ? 1 : lastItemNum + 1;
}
entity.setLineNum(lineNum);
entity = itemService.save(entity); entity = itemService.save(entity);
builder.createItem(entity); builder.createItem(entity);
} }
...@@ -157,4 +150,6 @@ public class ItemController { ...@@ -157,4 +150,6 @@ public class ItemController {
} }
return BeanUtils.transfrom(ItemDTO.class, item); return BeanUtils.transfrom(ItemDTO.class, item);
} }
} }
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.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.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.GrayReleaseRuleItemTransformer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NamespaceBranchController {
@Autowired
private MessageSender messageSender;
@Autowired
private NamespaceBranchService namespaceBranchService;
@RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches", method = RequestMethod.POST)
public NamespaceDTO createBranch(@PathVariable String appId,
@PathVariable String clusterName,
@PathVariable String namespaceName,
@RequestParam("operator") String operator) {
Namespace createdBranch = namespaceBranchService.createBranch(appId, clusterName, namespaceName, operator);
return BeanUtils.transfrom(NamespaceDTO.class, createdBranch);
}
@RequestMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules")
public GrayReleaseRuleDTO findBranchGrayRules(@PathVariable String appId,
@PathVariable String clusterName,
@PathVariable String namespaceName,
@PathVariable String 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;
}
@RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules", method = RequestMethod.PUT)
public void updateBranchGrayRules(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName, @PathVariable String branchName,
@RequestBody GrayReleaseRuleDTO newRuleDto) {
GrayReleaseRule newRules = BeanUtils.transfrom(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);
}
@RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}", method = RequestMethod.DELETE)
public void deleteBranch(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName, @PathVariable String branchName,
@RequestParam("operator") String operator) {
namespaceBranchService.deleteBranch(appId, clusterName, namespaceName, branchName, NamespaceBranchStatus.DELETED, operator);
messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
Topics.APOLLO_RELEASE_TOPIC);
}
@RequestMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches")
public NamespaceDTO loadNamespaceBranch(@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName) {
Namespace childNamespace = namespaceBranchService.findBranch(appId, clusterName, namespaceName);
if (childNamespace == null) {
return null;
}
return BeanUtils.transfrom(NamespaceDTO.class, childNamespace);
}
}
package com.ctrip.framework.apollo.adminservice.controller; package com.ctrip.framework.apollo.adminservice.controller;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.ctrip.framework.apollo.biz.entity.Namespace; import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release; import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.message.MessageSender; import com.ctrip.framework.apollo.biz.message.MessageSender;
import com.ctrip.framework.apollo.biz.message.Topics; 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.NamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService; 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.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO; import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.exception.NotFoundException; import com.ctrip.framework.apollo.common.exception.NotFoundException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@RestController @RestController
public class ReleaseController { public class ReleaseController {
private static final Splitter RELEASES_SPLITTER = Splitter.on(",").omitEmptyStrings()
.trimResults();
@Autowired @Autowired
private ReleaseService releaseService; private ReleaseService releaseService;
@Autowired @Autowired
private NamespaceService namespaceService; private NamespaceService namespaceService;
@Autowired @Autowired
private MessageSender messageSender; private MessageSender messageSender;
@Autowired
private NamespaceBranchService namespaceBranchService;
private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
@RequestMapping("/releases/{releaseId}") @RequestMapping("/releases/{releaseId}")
public ReleaseDTO get(@PathVariable("releaseId") long releaseId) { public ReleaseDTO get(@PathVariable("releaseId") long releaseId) {
...@@ -46,6 +56,16 @@ public class ReleaseController { ...@@ -46,6 +56,16 @@ public class ReleaseController {
return BeanUtils.transfrom(ReleaseDTO.class, release); return BeanUtils.transfrom(ReleaseDTO.class, release);
} }
@RequestMapping("/releases")
public List<ReleaseDTO> findReleaseByIds(@RequestParam("releaseIds") String releaseIds){
Set<Long> releaseIdSet = RELEASES_SPLITTER.splitToList(releaseIds).stream().map(Long::parseLong)
.collect(Collectors.toSet());
List<Release> releases = releaseService.findByReleaseIds(releaseIdSet);
return BeanUtils.batchTransform(ReleaseDTO.class, releases);
}
@RequestMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/all") @RequestMapping("/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/all")
public List<ReleaseDTO> findAllReleases(@PathVariable("appId") String appId, public List<ReleaseDTO> findAllReleases(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName, @PathVariable("clusterName") String clusterName,
...@@ -74,21 +94,66 @@ public class ReleaseController { ...@@ -74,21 +94,66 @@ public class ReleaseController {
} }
@RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST) @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST)
public ReleaseDTO buildRelease(@PathVariable("appId") String appId, public ReleaseDTO publish(@PathVariable("appId") String appId,
@PathVariable("clusterName") String clusterName, @PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName, @PathVariable("namespaceName") String namespaceName,
@RequestParam("name") String name, @RequestParam("name") String releaseName,
@RequestParam(name = "comment", required = false) String comment, @RequestParam(name = "comment", required = false) String releaseComment,
@RequestParam("operator") String operator) { @RequestParam("operator") String operator) {
Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName); Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
if (namespace == null) { if (namespace == null) {
throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId, throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId,
clusterName, namespaceName)); clusterName, namespaceName));
} }
Release release = releaseService.buildRelease(name, comment, namespace, operator); Release release = releaseService.publish(namespace, releaseName, releaseComment, operator);
messageSender.sendMessage(assembleKey(appId, clusterName, namespaceName),
//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.transfrom(ReleaseDTO.class, release);
}
/**
*merge branch items to master and publish master
* @return published result
*/
@Transactional
@RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/updateAndPublish", method = RequestMethod.POST)
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,
@RequestBody ItemChangeSets changeSets) {
Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
if (namespace == null) {
throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId,
clusterName, namespaceName));
}
Release release = releaseService.mergeBranchChangeSetsAndRelease(namespace, branchName,
releaseName, releaseComment, changeSets);
if (deleteBranch) {
namespaceBranchService.deleteBranch(appId, clusterName, namespaceName, branchName,
NamespaceBranchStatus.MERGED, changeSets.getDataChangeLastModifiedBy());
}
messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
Topics.APOLLO_RELEASE_TOPIC); Topics.APOLLO_RELEASE_TOPIC);
return BeanUtils.transfrom(ReleaseDTO.class, release); return BeanUtils.transfrom(ReleaseDTO.class, release);
} }
@RequestMapping(path = "/releases/{releaseId}/rollback", method = RequestMethod.PUT) @RequestMapping(path = "/releases/{releaseId}/rollback", method = RequestMethod.PUT)
...@@ -101,11 +166,8 @@ public class ReleaseController { ...@@ -101,11 +166,8 @@ public class ReleaseController {
String clusterName = release.getClusterName(); String clusterName = release.getClusterName();
String namespaceName = release.getNamespaceName(); String namespaceName = release.getNamespaceName();
//send release message //send release message
messageSender.sendMessage(assembleKey(appId, clusterName, namespaceName), messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
Topics.APOLLO_RELEASE_TOPIC); Topics.APOLLO_RELEASE_TOPIC);
} }
private String assembleKey(String appId, String cluster, String namespace) {
return STRING_JOINER.join(appId, cluster, namespace);
}
} }
package com.ctrip.framework.apollo.adminservice.controller;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.entity.ReleaseHistory;
import com.ctrip.framework.apollo.biz.repository.NamespaceRepository;
import com.ctrip.framework.apollo.biz.repository.ReleaseHistoryRepository;
import com.ctrip.framework.apollo.biz.repository.ReleaseRepository;
import com.ctrip.framework.apollo.biz.service.ReleaseHistoryService;
import com.ctrip.framework.apollo.common.constants.ReleaseOperation;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
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.Arrays;
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
public class ReleaseHistoryController {
private Gson gson = new Gson();
private Type configurationTypeReference = new TypeToken<Map<String, Object>>() {
}.getType();
@Autowired
private ReleaseHistoryService releaseHistoryService;
@Autowired
private ReleaseRepository releaseRepository;
@Autowired
private NamespaceRepository namespaceRepository;
@Autowired
private ReleaseHistoryRepository releaseHistoryRepository;
@RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/histories",
method = RequestMethod.GET)
public PageDTO<ReleaseHistoryDTO> findReleaseHistoriesByNamespace(
@PathVariable String appId, @PathVariable String clusterName,
@PathVariable String namespaceName, Pageable pageable) {
Page<ReleaseHistory> result = releaseHistoryService.findReleaseHistoriesByNamespace(appId,
clusterName, namespaceName, pageable);
if (!result.hasContent()) {
return null;
}
List<ReleaseHistory> releaseHistories = result.getContent();
List<ReleaseHistoryDTO> releaseHistoryDTOs = new ArrayList<>(releaseHistories.size());
for (ReleaseHistory releaseHistory : releaseHistories) {
ReleaseHistoryDTO dto = new ReleaseHistoryDTO();
BeanUtils.copyProperties(releaseHistory, dto, "operationContext");
dto.setOperationContext(gson.fromJson(releaseHistory.getOperationContext(),
configurationTypeReference));
releaseHistoryDTOs.add(dto);
}
return new PageDTO<>(releaseHistoryDTOs, pageable, result.getTotalElements());
}
@RequestMapping(value = "/release-histories/conversions", method = RequestMethod.POST)
public void releaseHistoryConversion(
@RequestParam(name = "namespaceId", required = false) String namespaceId) {
Iterable<Namespace> namespaces;
if (Strings.isNullOrEmpty(namespaceId)) {
namespaces = namespaceRepository.findAll();
} else {
Set<Long> idList = Arrays.stream(namespaceId.split(",")).map(Long::valueOf).collect
(Collectors.toSet());
namespaces = namespaceRepository.findAll(idList);
}
for (Namespace namespace : namespaces) {
List<Release> releases = releaseRepository
.findByAppIdAndClusterNameAndNamespaceNameOrderByIdAsc(namespace.getAppId(), namespace
.getClusterName(), namespace.getNamespaceName());
if (CollectionUtils.isEmpty(releases)) {
continue;
}
Release previousRelease = null;
Set<ReleaseHistory> releaseHistories = Sets.newLinkedHashSet();//ordered set
for (Release release : releases) {
List<ReleaseHistory> histories = releaseHistoryService.findReleaseHistoriesByReleaseId
(release.getId());
//already processed
if (!CollectionUtils.isEmpty(histories)) {
continue;
}
long previousReleaseId = previousRelease == null ? 0 : previousRelease.getId();
ReleaseHistory releaseHistory = assembleReleaseHistory(
release, ReleaseOperation .NORMAL_RELEASE, previousReleaseId);
releaseHistories.add(releaseHistory);
//rollback
if (release.isAbandoned()) {
releaseHistory.setDataChangeLastModifiedTime(release.getDataChangeCreatedTime());
ReleaseHistory rollBackReleaseHistory = assembleReleaseHistory(previousRelease,
ReleaseOperation.ROLLBACK, release.getId());
rollBackReleaseHistory.setDataChangeCreatedBy(release.getDataChangeLastModifiedBy());
rollBackReleaseHistory.setDataChangeCreatedTime(release.getDataChangeLastModifiedTime());
rollBackReleaseHistory.setDataChangeLastModifiedTime(release.getDataChangeLastModifiedTime());
releaseHistories.add(rollBackReleaseHistory);
} else {
previousRelease = release;
}
}
releaseHistoryRepository.save(releaseHistories);
}
}
public ReleaseHistory assembleReleaseHistory(Release release, int releaseOperation, long
previousReleaseId) {
ReleaseHistory releaseHistory = new ReleaseHistory();
releaseHistory.setAppId(release.getAppId());
releaseHistory.setClusterName(release.getClusterName());
releaseHistory.setNamespaceName(release.getNamespaceName());
releaseHistory.setBranchName(release.getClusterName());
releaseHistory.setReleaseId(release.getId());
releaseHistory.setPreviousReleaseId(previousReleaseId);
releaseHistory.setOperation(releaseOperation);
releaseHistory.setOperationContext("{}"); //default empty object
releaseHistory.setDataChangeCreatedBy(release.getDataChangeCreatedBy());
releaseHistory.setDataChangeCreatedTime(release.getDataChangeCreatedTime());
releaseHistory.setDataChangeLastModifiedTime(release.getDataChangeLastModifiedTime());
releaseHistory.setDataChangeLastModifiedBy("apollo"); //mark
return releaseHistory;
}
}
...@@ -13,6 +13,8 @@ endpoints: ...@@ -13,6 +13,8 @@ endpoints:
health: health:
sensitive: false sensitive: false
management: management:
security: security:
enabled: false enabled: false
......
...@@ -243,7 +243,7 @@ public class InstanceConfigControllerTest { ...@@ -243,7 +243,7 @@ public class InstanceConfigControllerTest {
pageable)).thenReturn(instances); pageable)).thenReturn(instances);
PageDTO<InstanceDTO> result = instanceConfigController.getInstancesByNamespace(someAppId, PageDTO<InstanceDTO> result = instanceConfigController.getInstancesByNamespace(someAppId,
someClusterName, someNamespaceName, pageable); someClusterName, someNamespaceName, null, pageable);
assertEquals(2, result.getContent().size()); assertEquals(2, result.getContent().size());
InstanceDTO someInstanceDto = null; InstanceDTO someInstanceDto = null;
...@@ -261,6 +261,47 @@ public class InstanceConfigControllerTest { ...@@ -261,6 +261,47 @@ public class InstanceConfigControllerTest {
verifyInstance(anotherInstance, anotherInstanceDto); verifyInstance(anotherInstance, anotherInstanceDto);
} }
@Test
public void testGetInstancesByNamespaceAndInstanceAppId() throws Exception {
String someInstanceAppId = "someInstanceAppId";
String someAppId = "someAppId";
String someClusterName = "someClusterName";
String someNamespaceName = "someNamespaceName";
String someIp = "someIp";
long someInstanceId = 1;
long anotherInstanceId = 2;
Pageable pageable = mock(Pageable.class);
Instance someInstance = assembleInstance(someInstanceId, someAppId, someClusterName,
someNamespaceName, someIp);
Instance anotherInstance = assembleInstance(anotherInstanceId, someAppId, someClusterName,
someNamespaceName, someIp);
Page<Instance> instances = new PageImpl<>(Lists.newArrayList(someInstance, anotherInstance),
pageable, 2);
when(instanceService.findInstancesByNamespaceAndInstanceAppId(someInstanceAppId, someAppId,
someClusterName, someNamespaceName, pageable)).thenReturn(instances);
PageDTO<InstanceDTO> result = instanceConfigController.getInstancesByNamespace(someAppId,
someClusterName, someNamespaceName, someInstanceAppId, pageable);
assertEquals(2, result.getContent().size());
InstanceDTO someInstanceDto = null;
InstanceDTO anotherInstanceDto = null;
for (InstanceDTO instanceDTO : result.getContent()) {
if (instanceDTO.getId() == someInstanceId) {
someInstanceDto = instanceDTO;
} else if (instanceDTO.getId() == anotherInstanceId) {
anotherInstanceDto = instanceDTO;
}
}
verifyInstance(someInstance, someInstanceDto);
verifyInstance(anotherInstance, anotherInstanceDto);
}
@Test @Test
public void testGetInstancesCountByNamespace() throws Exception { public void testGetInstancesCountByNamespace() throws Exception {
String someAppId = "someAppId"; String someAppId = "someAppId";
......
...@@ -121,7 +121,7 @@ public class ReleaseControllerTest extends AbstractControllerTest { ...@@ -121,7 +121,7 @@ public class ReleaseControllerTest extends AbstractControllerTest {
.thenReturn(someNamespace); .thenReturn(someNamespace);
releaseController releaseController
.buildRelease(someAppId, someCluster, someNamespaceName, someName, someComment, "test"); .publish(someAppId, someCluster, someNamespaceName, someName, someComment, "test");
verify(someMessageSender, times(1)) verify(someMessageSender, times(1))
.sendMessage(Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR) .sendMessage(Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
......
...@@ -6,3 +6,6 @@ logging: ...@@ -6,3 +6,6 @@ logging:
level: level:
org.springframework.cloud: 'DEBUG' org.springframework.cloud: 'DEBUG'
file: /opt/logs/100003171/apollo-assembly.log file: /opt/logs/100003171/apollo-assembly.log
...@@ -24,6 +24,9 @@ public class Cluster extends BaseEntity implements Comparable<Cluster> { ...@@ -24,6 +24,9 @@ public class Cluster extends BaseEntity implements Comparable<Cluster> {
@Column(name = "AppId", nullable = false) @Column(name = "AppId", nullable = false)
private String appId; private String appId;
@Column(name = "ParentClusterId", nullable = false)
private long parentClusterId;
public String getAppId() { public String getAppId() {
return appId; return appId;
} }
...@@ -40,8 +43,17 @@ public class Cluster extends BaseEntity implements Comparable<Cluster> { ...@@ -40,8 +43,17 @@ public class Cluster extends BaseEntity implements Comparable<Cluster> {
this.name = name; this.name = name;
} }
public long getParentClusterId() {
return parentClusterId;
}
public void setParentClusterId(long parentClusterId) {
this.parentClusterId = parentClusterId;
}
public String toString() { public String toString() {
return toStringHelper().add("name", name).add("appId", appId).toString(); return toStringHelper().add("name", name).add("appId", appId)
.add("parentClusterId", parentClusterId).toString();
} }
@Override @Override
......
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "GrayReleaseRule")
@SQLDelete(sql = "Update GrayReleaseRule set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class GrayReleaseRule extends BaseEntity{
@Column(name = "appId", nullable = false)
private String appId;
@Column(name = "ClusterName", nullable = false)
private String clusterName;
@Column(name = "NamespaceName", nullable = false)
private String namespaceName;
@Column(name = "BranchName", nullable = false)
private String branchName;
@Column(name = "Rules")
private String rules;
@Column(name = "releaseId", nullable = false)
private Long releaseId;
@Column(name = "BranchStatus", nullable = false)
private int branchStatus;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public String getNamespaceName() {
return namespaceName;
}
public void setNamespaceName(String namespaceName) {
this.namespaceName = namespaceName;
}
public String getBranchName() {
return branchName;
}
public void setBranchName(String branchName) {
this.branchName = branchName;
}
public String getRules() {
return rules;
}
public void setRules(String rules) {
this.rules = rules;
}
public Long getReleaseId() {
return releaseId;
}
public void setReleaseId(Long releaseId) {
this.releaseId = releaseId;
}
public int getBranchStatus() {
return branchStatus;
}
public void setBranchStatus(int branchStatus) {
this.branchStatus = branchStatus;
}
}
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Entity
@Table(name = "ReleaseHistory")
@SQLDelete(sql = "Update ReleaseHistory set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class ReleaseHistory extends BaseEntity {
@Column(name = "AppId", nullable = false)
private String appId;
@Column(name = "ClusterName", nullable = false)
private String clusterName;
@Column(name = "NamespaceName", nullable = false)
private String namespaceName;
@Column(name = "BranchName", nullable = false)
private String branchName;
@Column(name = "ReleaseId")
private long releaseId;
@Column(name = "PreviousReleaseId")
private long previousReleaseId;
@Column(name = "Operation")
private int operation;
@Column(name = "OperationContext", nullable = false)
private String operationContext;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public String getNamespaceName() {
return namespaceName;
}
public void setNamespaceName(String namespaceName) {
this.namespaceName = namespaceName;
}
public String getBranchName() {
return branchName;
}
public void setBranchName(String branchName) {
this.branchName = branchName;
}
public long getReleaseId() {
return releaseId;
}
public void setReleaseId(long releaseId) {
this.releaseId = releaseId;
}
public long getPreviousReleaseId() {
return previousReleaseId;
}
public void setPreviousReleaseId(long previousReleaseId) {
this.previousReleaseId = previousReleaseId;
}
public int getOperation() {
return operation;
}
public void setOperation(int operation) {
this.operation = operation;
}
public String getOperationContext() {
return operationContext;
}
public void setOperationContext(String operationContext) {
this.operationContext = operationContext;
}
public String toString() {
return toStringHelper().add("appId", appId).add("clusterName", clusterName)
.add("namespaceName", namespaceName).add("branchName", branchName)
.add("releaseId", releaseId).add("previousReleaseId", previousReleaseId)
.add("operation", operation).toString();
}
}
package com.ctrip.framework.apollo.biz.grayReleaseRule;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleItemDTO;
import java.util.Set;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class GrayReleaseRuleCache {
private long ruleId;
private String branchName;
private String namespaceName;
private long releaseId;
private long loadVersion;
private int branchStatus;
private Set<GrayReleaseRuleItemDTO> ruleItems;
public GrayReleaseRuleCache(long ruleId, String branchName, String namespaceName, long
releaseId, int branchStatus, long loadVersion, Set<GrayReleaseRuleItemDTO> ruleItems) {
this.ruleId = ruleId;
this.branchName = branchName;
this.namespaceName = namespaceName;
this.releaseId = releaseId;
this.branchStatus = branchStatus;
this.loadVersion = loadVersion;
this.ruleItems = ruleItems;
}
public long getRuleId() {
return ruleId;
}
public Set<GrayReleaseRuleItemDTO> getRuleItems() {
return ruleItems;
}
public String getBranchName() {
return branchName;
}
public int getBranchStatus() {
return branchStatus;
}
public long getReleaseId() {
return releaseId;
}
public long getLoadVersion() {
return loadVersion;
}
public void setLoadVersion(long loadVersion) {
this.loadVersion = loadVersion;
}
public String getNamespaceName() {
return namespaceName;
}
public boolean matches(String clientAppId, String clientIp) {
for (GrayReleaseRuleItemDTO ruleItem : ruleItems) {
if (ruleItem.matches(clientAppId, clientIp)) {
return true;
}
}
return false;
}
}
package com.ctrip.framework.apollo.biz.grayReleaseRule;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.ctrip.framework.apollo.biz.entity.GrayReleaseRule;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.message.ReleaseMessageListener;
import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.repository.GrayReleaseRuleRepository;
import com.ctrip.framework.apollo.biz.service.ServerConfigService;
import com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleItemDTO;
import com.ctrip.framework.apollo.common.utils.GrayReleaseRuleItemTransformer;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class GrayReleaseRulesHolder implements ReleaseMessageListener, InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(GrayReleaseRulesHolder.class);
private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
private static final Splitter STRING_SPLITTER =
Splitter.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).omitEmptyStrings();
private static final int DEFAULT_SCAN_INTERVAL_IN_SECONDS = 60;
@Autowired
private ServerConfigService serverConfigService;
@Autowired
private GrayReleaseRuleRepository grayReleaseRuleRepository;
private int databaseScanInterval;
private ScheduledExecutorService executorService;
//store configAppId+configCluster+configNamespace -> GrayReleaseRuleCache map
private Multimap<String, GrayReleaseRuleCache> grayReleaseRuleCache;
//store clientAppId+clientNamespace+ip -> ruleId map
private Multimap<String, Long> reversedGrayReleaseRuleCache;
//an auto increment version to indicate the age of rules
private AtomicLong loadVersion;
public GrayReleaseRulesHolder() {
loadVersion = new AtomicLong();
grayReleaseRuleCache = Multimaps.synchronizedSetMultimap(HashMultimap.create());
reversedGrayReleaseRuleCache = Multimaps.synchronizedSetMultimap(HashMultimap.create());
executorService = Executors.newScheduledThreadPool(1, ApolloThreadFactory
.create("GrayReleaseRulesHolder", true));
}
@Override
public void afterPropertiesSet() throws Exception {
populateDataBaseInterval();
//force sync load for the first time
periodicScanRules();
executorService.scheduleWithFixedDelay(this::periodicScanRules,
getDatabaseScanIntervalSecond(), getDatabaseScanIntervalSecond(), getDatabaseScanTimeUnit()
);
}
@Override
public void handleMessage(ReleaseMessage message, String channel) {
logger.info("message received - channel: {}, message: {}", channel, message);
String releaseMessage = message.getMessage();
if (!Topics.APOLLO_RELEASE_TOPIC.equals(channel) || Strings.isNullOrEmpty(releaseMessage)) {
return;
}
List<String> keys = STRING_SPLITTER.splitToList(releaseMessage);
//message should be appId+cluster+namespace
if (keys.size() != 3) {
logger.error("message format invalid - {}", releaseMessage);
return;
}
String appId = keys.get(0);
String cluster = keys.get(1);
String namespace = keys.get(2);
List<GrayReleaseRule> rules = grayReleaseRuleRepository
.findByAppIdAndClusterNameAndNamespaceName(appId, cluster, namespace);
mergeGrayReleaseRules(rules);
}
private void periodicScanRules() {
Transaction transaction = Cat.newTransaction("Apollo.GrayReleaseRulesScanner",
"scanGrayReleaseRules");
try {
loadVersion.incrementAndGet();
scanGrayReleaseRules();
transaction.setStatus(Message.SUCCESS);
} catch (Throwable ex) {
transaction.setStatus(ex);
logger.error("Scan gray release rule failed", ex);
} finally {
transaction.complete();
}
}
public Long findReleaseIdFromGrayReleaseRule(String clientAppId, String clientIp, String
configAppId, String configCluster, String configNamespaceName) {
String key = assembleGrayReleaseRuleKey(configAppId, configCluster, configNamespaceName);
if (!grayReleaseRuleCache.containsKey(key)) {
return null;
}
//create a new list to avoid ConcurrentModificationException
List<GrayReleaseRuleCache> rules = Lists.newArrayList(grayReleaseRuleCache.get(key));
for (GrayReleaseRuleCache rule : rules) {
//check branch status
if (rule.getBranchStatus() != NamespaceBranchStatus.ACTIVE) {
continue;
}
if (rule.matches(clientAppId, clientIp)) {
return rule.getReleaseId();
}
}
return null;
}
/**
* Check whether there are gray release rules for the clientAppId, clientIp, namespace
* combination. Please note that even there are gray release rules, it doesn't mean it will always
* load gray releases. Because gray release rules actually apply to one more dimension - cluster.
*/
public boolean hasGrayReleaseRule(String clientAppId, String clientIp, String namespaceName) {
return reversedGrayReleaseRuleCache.containsKey(assembleReversedGrayReleaseRuleKey(clientAppId,
namespaceName, clientIp)) || reversedGrayReleaseRuleCache.containsKey
(assembleReversedGrayReleaseRuleKey(clientAppId, namespaceName, GrayReleaseRuleItemDTO
.ALL_IP));
}
private void scanGrayReleaseRules() {
long maxIdScanned = 0;
boolean hasMore = true;
while (hasMore && !Thread.currentThread().isInterrupted()) {
List<GrayReleaseRule> grayReleaseRules = grayReleaseRuleRepository
.findFirst500ByIdGreaterThanOrderByIdAsc(maxIdScanned);
if (CollectionUtils.isEmpty(grayReleaseRules)) {
break;
}
mergeGrayReleaseRules(grayReleaseRules);
int rulesScanned = grayReleaseRules.size();
maxIdScanned = grayReleaseRules.get(rulesScanned - 1).getId();
//batch is 500
hasMore = rulesScanned == 500;
}
}
private void mergeGrayReleaseRules(List<GrayReleaseRule> grayReleaseRules) {
if (CollectionUtils.isEmpty(grayReleaseRules)) {
return;
}
for (GrayReleaseRule grayReleaseRule : grayReleaseRules) {
if (grayReleaseRule.getReleaseId() == null || grayReleaseRule.getReleaseId() == 0) {
//filter rules with no release id, i.e. never released
continue;
}
String key = assembleGrayReleaseRuleKey(grayReleaseRule.getAppId(), grayReleaseRule
.getClusterName(), grayReleaseRule.getNamespaceName());
//create a new list to avoid ConcurrentModificationException
List<GrayReleaseRuleCache> rules = Lists.newArrayList(grayReleaseRuleCache.get(key));
GrayReleaseRuleCache oldRule = null;
for (GrayReleaseRuleCache ruleCache : rules) {
if (ruleCache.getBranchName().equals(grayReleaseRule.getBranchName())) {
oldRule = ruleCache;
break;
}
}
//if old rule is null and new rule's branch status is not active, ignore
if (oldRule == null && grayReleaseRule.getBranchStatus() != NamespaceBranchStatus.ACTIVE) {
continue;
}
//use id comparison to avoid synchronization
if (oldRule == null || grayReleaseRule.getId() > oldRule.getRuleId()) {
addCache(key, transformRuleToRuleCache(grayReleaseRule));
if (oldRule != null) {
removeCache(key, oldRule);
}
} else {
if (oldRule.getBranchStatus() == NamespaceBranchStatus.ACTIVE) {
//update load version
oldRule.setLoadVersion(loadVersion.get());
} else if ((loadVersion.get() - oldRule.getLoadVersion()) > 1) {
//remove outdated inactive branch rule after 2 update cycles
removeCache(key, oldRule);
}
}
}
}
private void addCache(String key, GrayReleaseRuleCache ruleCache) {
if (ruleCache.getBranchStatus() == NamespaceBranchStatus.ACTIVE) {
for (GrayReleaseRuleItemDTO ruleItemDTO : ruleCache.getRuleItems()) {
for (String clientIp : ruleItemDTO.getClientIpList()) {
reversedGrayReleaseRuleCache.put(assembleReversedGrayReleaseRuleKey(ruleItemDTO
.getClientAppId(), ruleCache.getNamespaceName(), clientIp), ruleCache.getRuleId());
}
}
}
grayReleaseRuleCache.put(key, ruleCache);
}
private void removeCache(String key, GrayReleaseRuleCache ruleCache) {
grayReleaseRuleCache.remove(key, ruleCache);
for (GrayReleaseRuleItemDTO ruleItemDTO : ruleCache.getRuleItems()) {
for (String clientIp : ruleItemDTO.getClientIpList()) {
reversedGrayReleaseRuleCache.remove(assembleReversedGrayReleaseRuleKey(ruleItemDTO
.getClientAppId(), ruleCache.getNamespaceName(), clientIp), ruleCache.getRuleId());
}
}
}
private GrayReleaseRuleCache transformRuleToRuleCache(GrayReleaseRule grayReleaseRule) {
Set<GrayReleaseRuleItemDTO> ruleItems;
try {
ruleItems = GrayReleaseRuleItemTransformer.batchTransformFromJSON(grayReleaseRule.getRules());
} catch (Throwable ex) {
ruleItems = Sets.newHashSet();
Cat.logError(ex);
logger.error("parse rule for gray release rule {} failed", grayReleaseRule.getId(), ex);
}
GrayReleaseRuleCache ruleCache = new GrayReleaseRuleCache(grayReleaseRule.getId(),
grayReleaseRule.getBranchName(), grayReleaseRule.getNamespaceName(), grayReleaseRule
.getReleaseId(), grayReleaseRule.getBranchStatus(), loadVersion.get(), ruleItems);
return ruleCache;
}
private void populateDataBaseInterval() {
databaseScanInterval = DEFAULT_SCAN_INTERVAL_IN_SECONDS;
try {
String interval = serverConfigService.getValue("apollo.gray-release-rule-scan.interval");
if (!Objects.isNull(interval)) {
databaseScanInterval = Integer.parseInt(interval);
}
} catch (Throwable ex) {
Cat.logError(ex);
logger.error("Load apollo gray release rule scan interval from server config failed", ex);
}
}
private int getDatabaseScanIntervalSecond() {
return databaseScanInterval;
}
private TimeUnit getDatabaseScanTimeUnit() {
return TimeUnit.SECONDS;
}
private String assembleGrayReleaseRuleKey(String configAppId, String configCluster, String
configNamespaceName) {
return STRING_JOINER.join(configAppId, configCluster, configNamespaceName);
}
private String assembleReversedGrayReleaseRuleKey(String clientAppId, String
clientNamespaceName, String clientIp) {
return STRING_JOINER.join(clientAppId, clientNamespaceName, clientIp);
}
}
...@@ -9,7 +9,11 @@ import java.util.List; ...@@ -9,7 +9,11 @@ import java.util.List;
public interface ClusterRepository extends PagingAndSortingRepository<Cluster, Long> { public interface ClusterRepository extends PagingAndSortingRepository<Cluster, Long> {
List<Cluster> findByAppIdAndParentClusterId(String appId, Long parentClusterId);
List<Cluster> findByAppId(String appId); List<Cluster> findByAppId(String appId);
Cluster findByAppIdAndName(String appId, String name); Cluster findByAppIdAndName(String appId, String name);
List<Cluster> findByParentClusterId(Long parentClusterId);
} }
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.GrayReleaseRule;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
public interface GrayReleaseRuleRepository extends PagingAndSortingRepository<GrayReleaseRule, Long> {
GrayReleaseRule findTopByAppIdAndClusterNameAndNamespaceNameAndBranchNameOrderByIdDesc(String appId, String clusterName,
String namespaceName, String branchName);
List<GrayReleaseRule> findByAppIdAndClusterNameAndNamespaceName(String appId,
String clusterName, String namespaceName);
List<GrayReleaseRule> findFirst500ByIdGreaterThanOrderByIdAsc(Long id);
}
...@@ -4,7 +4,9 @@ import com.ctrip.framework.apollo.biz.entity.InstanceConfig; ...@@ -4,7 +4,9 @@ import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
...@@ -12,8 +14,8 @@ import java.util.Set; ...@@ -12,8 +14,8 @@ import java.util.Set;
public interface InstanceConfigRepository extends PagingAndSortingRepository<InstanceConfig, Long> { public interface InstanceConfigRepository extends PagingAndSortingRepository<InstanceConfig, Long> {
InstanceConfig findByInstanceIdAndConfigAppIdAndConfigClusterNameAndConfigNamespaceName(long instanceId, String InstanceConfig findByInstanceIdAndConfigAppIdAndConfigNamespaceName(long instanceId, String
configAppId, String configClusterName, String configNamespaceName); configAppId, String configNamespaceName);
Page<InstanceConfig> findByReleaseKeyAndDataChangeLastModifiedTimeAfter(String releaseKey, Date Page<InstanceConfig> findByReleaseKeyAndDataChangeLastModifiedTimeAfter(String releaseKey, Date
validDate, Pageable pageable); validDate, Pageable pageable);
...@@ -24,4 +26,18 @@ public interface InstanceConfigRepository extends PagingAndSortingRepository<Ins ...@@ -24,4 +26,18 @@ public interface InstanceConfigRepository extends PagingAndSortingRepository<Ins
List<InstanceConfig> findByConfigAppIdAndConfigClusterNameAndConfigNamespaceNameAndDataChangeLastModifiedTimeAfterAndReleaseKeyNotIn( List<InstanceConfig> findByConfigAppIdAndConfigClusterNameAndConfigNamespaceNameAndDataChangeLastModifiedTimeAfterAndReleaseKeyNotIn(
String appId, String clusterName, String namespaceName, Date validDate, Set<String> releaseKey); String appId, String clusterName, String namespaceName, Date validDate, Set<String> releaseKey);
@Query(
value = "select b.Id from `InstanceConfig` a inner join `Instance` b on b.Id =" +
" a.`InstanceId` where a.`ConfigAppId` = :configAppId and a.`ConfigClusterName` = " +
":clusterName and a.`ConfigNamespaceName` = :namespaceName and a.`DataChange_LastTime` " +
"> :validDate and b.`AppId` = :instanceAppId and ?#{#pageable.pageSize} > 0",
countQuery = "select count(1) from `InstanceConfig` a inner join `Instance` b on b.id =" +
" a.`InstanceId` where a.`ConfigAppId` = :configAppId and a.`ConfigClusterName` = " +
":clusterName and a.`ConfigNamespaceName` = :namespaceName and a.`DataChange_LastTime` " +
"> :validDate and b.`AppId` = :instanceAppId",
nativeQuery = true)
Page<Object[]> findInstanceIdsByNamespaceAndInstanceAppId(
@Param("instanceAppId") String instanceAppId, @Param("configAppId") String configAppId,
@Param("clusterName") String clusterName, @Param("namespaceName") String namespaceName,
@Param("validDate") Date validDate, Pageable pageable);
} }
...@@ -18,4 +18,5 @@ public interface NamespaceRepository extends PagingAndSortingRepository<Namespac ...@@ -18,4 +18,5 @@ public interface NamespaceRepository extends PagingAndSortingRepository<Namespac
@Query("update Namespace set isdeleted=1,DataChange_LastModifiedBy = ?3 where appId=?1 and clusterName=?2") @Query("update Namespace set isdeleted=1,DataChange_LastModifiedBy = ?3 where appId=?1 and clusterName=?2")
int batchDelete(String appId, String clusterName, String operator); int batchDelete(String appId, String clusterName, String operator);
List<Namespace> findByAppIdAndNamespaceName(String appId, String namespaceName);
} }
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.ReleaseHistory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public interface ReleaseHistoryRepository extends PagingAndSortingRepository<ReleaseHistory, Long> {
Page<ReleaseHistory> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String
clusterName, String namespaceName, Pageable pageable);
List<ReleaseHistory> findByReleaseId(long releaseId);
}
...@@ -19,13 +19,20 @@ public interface ReleaseRepository extends PagingAndSortingRepository<Release, L ...@@ -19,13 +19,20 @@ public interface ReleaseRepository extends PagingAndSortingRepository<Release, L
Release findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(@Param("appId") String appId, @Param("clusterName") String clusterName, Release findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(@Param("appId") String appId, @Param("clusterName") String clusterName,
@Param("namespaceName") String namespaceName); @Param("namespaceName") String namespaceName);
Release findByIdAndIsAbandonedFalse(long id);
List<Release> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page); List<Release> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page);
List<Release> findByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page); List<Release> findByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page);
List<Release> findByReleaseKeyIn(Set<String> releaseKey); List<Release> findByReleaseKeyIn(Set<String> releaseKey);
List<Release> findByIdIn(Set<Long> releaseIds);
@Modifying @Modifying
@Query("update Release set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3") @Query("update Release set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3")
int batchDelete(String appId, String clusterName, String namespaceName, String operator); int batchDelete(String appId, String clusterName, String namespaceName, String operator);
// For release history conversion program, need to delete after conversion it done
List<Release> findByAppIdAndClusterNameAndNamespaceNameOrderByIdAsc(String appId, String clusterName, String namespaceName);
} }
...@@ -122,7 +122,7 @@ public class AppNamespaceService { ...@@ -122,7 +122,7 @@ public class AppNamespaceService {
} }
private void linkPrivateAppNamespaceInAllCluster(String appId, String namespaceName, String createBy) { private void linkPrivateAppNamespaceInAllCluster(String appId, String namespaceName, String createBy) {
List<Cluster> clusters = clusterService.findClusters(appId); List<Cluster> clusters = clusterService.findParentClusters(appId);
for (Cluster cluster : clusters) { for (Cluster cluster : clusters) {
Namespace namespace = new Namespace(); Namespace namespace = new Namespace();
namespace.setClusterName(cluster.getName()); namespace.setClusterName(cluster.getName());
......
...@@ -39,12 +39,16 @@ public class ClusterService { ...@@ -39,12 +39,16 @@ public class ClusterService {
return clusterRepository.findByAppIdAndName(appId, name); return clusterRepository.findByAppIdAndName(appId, name);
} }
public List<Cluster> findClusters(String appId) { public Cluster findOne(long clusterId){
return clusterRepository.findOne(clusterId);
}
public List<Cluster> findParentClusters(String appId) {
if (Strings.isNullOrEmpty(appId)) { if (Strings.isNullOrEmpty(appId)) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<Cluster> clusters = clusterRepository.findByAppId(appId); List<Cluster> clusters = clusterRepository.findByAppIdAndParentClusterId(appId, 0L);
if (clusters == null) { if (clusters == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
...@@ -55,15 +59,24 @@ public class ClusterService { ...@@ -55,15 +59,24 @@ public class ClusterService {
} }
@Transactional @Transactional
public Cluster save(Cluster entity) { public Cluster saveWithCreatePrivateNamespace(Cluster entity) {
Cluster savedCluster = saveWithoutCreatePrivateNamespace(entity);
namespaceService.createPrivateNamespace(savedCluster.getAppId(), savedCluster.getName(),
savedCluster.getDataChangeCreatedBy());
return savedCluster;
}
@Transactional
public Cluster saveWithoutCreatePrivateNamespace(Cluster entity){
if (!isClusterNameUnique(entity.getAppId(), entity.getName())) { if (!isClusterNameUnique(entity.getAppId(), entity.getName())) {
throw new ServiceException("cluster not unique"); throw new BadRequestException("cluster not unique");
} }
entity.setId(0);//protection entity.setId(0);//protection
Cluster cluster = clusterRepository.save(entity); Cluster cluster = clusterRepository.save(entity);
namespaceService.createPrivateNamespace(cluster.getAppId(), cluster.getName(), cluster.getDataChangeCreatedBy());
auditService.audit(Cluster.class.getSimpleName(), cluster.getId(), Audit.OP.INSERT, auditService.audit(Cluster.class.getSimpleName(), cluster.getId(), Audit.OP.INSERT,
cluster.getDataChangeCreatedBy()); cluster.getDataChangeCreatedBy());
...@@ -114,4 +127,14 @@ public class ClusterService { ...@@ -114,4 +127,14 @@ public class ClusterService {
auditService.audit(Cluster.class.getSimpleName(), cluster.getId(), Audit.OP.INSERT, createBy); auditService.audit(Cluster.class.getSimpleName(), cluster.getId(), Audit.OP.INSERT, createBy);
} }
public List<Cluster> findChildClusters(String appId, String parentClusterName){
Cluster parentCluster = findOne(appId, parentClusterName);
if (parentCluster == null){
throw new BadRequestException("parent cluster not exist");
}
return clusterRepository.findByParentClusterId(parentCluster.getId());
}
} }
package com.ctrip.framework.apollo.biz.service; package com.ctrip.framework.apollo.biz.service;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.Interner;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.ctrip.framework.apollo.biz.entity.Instance; import com.ctrip.framework.apollo.biz.entity.Instance;
...@@ -16,6 +17,7 @@ import org.springframework.stereotype.Service; ...@@ -16,6 +17,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import java.math.BigInteger;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
...@@ -55,10 +57,10 @@ public class InstanceService { ...@@ -55,10 +57,10 @@ public class InstanceService {
} }
public InstanceConfig findInstanceConfig(long instanceId, String configAppId, String public InstanceConfig findInstanceConfig(long instanceId, String configAppId, String
configClusterName, String configNamespaceName) { configNamespaceName) {
return instanceConfigRepository return instanceConfigRepository
.findByInstanceIdAndConfigAppIdAndConfigClusterNameAndConfigNamespaceName( .findByInstanceIdAndConfigAppIdAndConfigNamespaceName(
instanceId, configAppId, configClusterName, configNamespaceName); instanceId, configAppId, configNamespaceName);
} }
public Page<InstanceConfig> findActiveInstanceConfigsByReleaseKey(String releaseKey, Pageable public Page<InstanceConfig> findActiveInstanceConfigsByReleaseKey(String releaseKey, Pageable
...@@ -85,6 +87,42 @@ public class InstanceService { ...@@ -85,6 +87,42 @@ public class InstanceService {
return new PageImpl<>(instances, pageable, instanceConfigs.getTotalElements()); return new PageImpl<>(instances, pageable, instanceConfigs.getTotalElements());
} }
public Page<Instance> findInstancesByNamespaceAndInstanceAppId(String instanceAppId, String
appId, String clusterName, String
namespaceName, Pageable
pageable) {
Page<Object[]> instanceIdResult = instanceConfigRepository
.findInstanceIdsByNamespaceAndInstanceAppId(instanceAppId, appId, clusterName,
namespaceName, getValidInstanceConfigDate(), pageable);
List<Instance> instances = Collections.emptyList();
if (instanceIdResult.hasContent()) {
Set<Long> instanceIds = instanceIdResult.getContent().stream().map((Object o) -> {
if (o == null) {
return null;
}
if (o instanceof Integer) {
return ((Integer)o).longValue();
}
if (o instanceof Long) {
return (Long) o;
}
//for h2 test
if (o instanceof BigInteger) {
return ((BigInteger) o).longValue();
}
return null;
}).filter((Long value) -> value != null).collect(Collectors.toSet());
instances = findInstancesByIds(instanceIds);
}
return new PageImpl<>(instances, pageable, instanceIdResult.getTotalElements());
}
public List<InstanceConfig> findInstanceConfigsByNamespaceWithReleaseKeysNotIn(String appId, public List<InstanceConfig> findInstanceConfigsByNamespaceWithReleaseKeysNotIn(String appId,
String clusterName, String clusterName,
String String
...@@ -126,6 +164,7 @@ public class InstanceService { ...@@ -126,6 +164,7 @@ public class InstanceService {
Preconditions.checkArgument(existedInstanceConfig != null, String.format( Preconditions.checkArgument(existedInstanceConfig != null, String.format(
"Instance config %d doesn't exist", instanceConfig.getId())); "Instance config %d doesn't exist", instanceConfig.getId()));
existedInstanceConfig.setConfigClusterName(instanceConfig.getConfigClusterName());
existedInstanceConfig.setReleaseKey(instanceConfig.getReleaseKey()); existedInstanceConfig.setReleaseKey(instanceConfig.getReleaseKey());
existedInstanceConfig.setReleaseDeliveryTime(instanceConfig.getReleaseDeliveryTime()); existedInstanceConfig.setReleaseDeliveryTime(instanceConfig.getReleaseDeliveryTime());
existedInstanceConfig.setDataChangeLastModifiedTime(instanceConfig existedInstanceConfig.setDataChangeLastModifiedTime(instanceConfig
......
...@@ -4,7 +4,6 @@ import com.ctrip.framework.apollo.biz.entity.Audit; ...@@ -4,7 +4,6 @@ import com.ctrip.framework.apollo.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.entity.Item; import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace; import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.repository.ItemRepository; import com.ctrip.framework.apollo.biz.repository.ItemRepository;
import com.ctrip.framework.apollo.biz.repository.NamespaceRepository;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException; import com.ctrip.framework.apollo.common.exception.NotFoundException;
...@@ -24,7 +23,7 @@ public class ItemService { ...@@ -24,7 +23,7 @@ public class ItemService {
private ItemRepository itemRepository; private ItemRepository itemRepository;
@Autowired @Autowired
private NamespaceRepository namespaceRepository; private NamespaceService namespaceService;
@Autowired @Autowired
private AuditService auditService; private AuditService auditService;
...@@ -54,8 +53,7 @@ public class ItemService { ...@@ -54,8 +53,7 @@ public class ItemService {
} }
public Item findOne(String appId, String clusterName, String namespaceName, String key) { public Item findOne(String appId, String clusterName, String namespaceName, String key) {
Namespace namespace = namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
clusterName, namespaceName);
if (namespace == null) { if (namespace == null) {
throw new NotFoundException( throw new NotFoundException(
String.format("namespace not found for %s %s %s", appId, clusterName, namespaceName)); String.format("namespace not found for %s %s %s", appId, clusterName, namespaceName));
...@@ -65,14 +63,16 @@ public class ItemService { ...@@ -65,14 +63,16 @@ public class ItemService {
} }
public Item findLastOne(String appId, String clusterName, String namespaceName) { public Item findLastOne(String appId, String clusterName, String namespaceName) {
Namespace namespace = namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
clusterName, namespaceName);
if (namespace == null) { if (namespace == null) {
throw new NotFoundException( throw new NotFoundException(
String.format("namespace not found for %s %s %s", appId, clusterName, namespaceName)); String.format("namespace not found for %s %s %s", appId, clusterName, namespaceName));
} }
Item item = itemRepository.findFirst1ByNamespaceIdOrderByLineNumDesc(namespace.getId()); return findLastOne(namespace.getId());
return item; }
public Item findLastOne(long namespaceId) {
return itemRepository.findFirst1ByNamespaceIdOrderByLineNumDesc(namespaceId);
} }
public Item findOne(long itemId) { public Item findOne(long itemId) {
...@@ -89,8 +89,7 @@ public class ItemService { ...@@ -89,8 +89,7 @@ public class ItemService {
} }
public List<Item> findItems(String appId, String clusterName, String namespaceName) { public List<Item> findItems(String appId, String clusterName, String namespaceName) {
Namespace namespace = namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, clusterName, Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
namespaceName);
if (namespace != null) { if (namespace != null) {
return findItems(namespace.getId()); return findItems(namespace.getId());
} else { } else {
...@@ -104,6 +103,13 @@ public class ItemService { ...@@ -104,6 +103,13 @@ public class ItemService {
checkItemValueLength(entity.getValue()); checkItemValueLength(entity.getValue());
entity.setId(0);//protection entity.setId(0);//protection
if (entity.getLineNum() == 0) {
Item lastItem = findLastOne(entity.getNamespaceId());
int lineNum = lastItem == null ? 1 : lastItem.getLineNum() + 1;
entity.setLineNum(lineNum);
}
Item item = itemRepository.save(entity); Item item = itemRepository.save(entity);
auditService.audit(Item.class.getSimpleName(), item.getId(), Audit.OP.INSERT, auditService.audit(Item.class.getSimpleName(), item.getId(), Audit.OP.INSERT,
...@@ -125,17 +131,17 @@ public class ItemService { ...@@ -125,17 +131,17 @@ public class ItemService {
return managedItem; return managedItem;
} }
private boolean checkItemValueLength(String value){ private boolean checkItemValueLength(String value) {
int lengthLimit = Integer.valueOf(serverConfigService.getValue("item.value.length.limit", "20000")); int lengthLimit = Integer.valueOf(serverConfigService.getValue("item.value.length.limit", "20000"));
if (!StringUtils.isEmpty(value) && value.length() > lengthLimit){ if (!StringUtils.isEmpty(value) && value.length() > lengthLimit) {
throw new BadRequestException("value too long. length limit:" + lengthLimit); throw new BadRequestException("value too long. length limit:" + lengthLimit);
} }
return true; return true;
} }
private boolean checkItemKeyLength(String key){ private boolean checkItemKeyLength(String key) {
int lengthLimit = Integer.valueOf(serverConfigService.getValue("item.key.length.limit", "128")); int lengthLimit = Integer.valueOf(serverConfigService.getValue("item.key.length.limit", "128"));
if (!StringUtils.isEmpty(key) && key.length() > lengthLimit){ if (!StringUtils.isEmpty(key) && key.length() > lengthLimit) {
throw new BadRequestException("key too long. length limit:" + lengthLimit); throw new BadRequestException("key too long. length limit:" + lengthLimit);
} }
return true; return true;
......
...@@ -8,6 +8,7 @@ import org.springframework.util.CollectionUtils; ...@@ -8,6 +8,7 @@ import org.springframework.util.CollectionUtils;
import com.ctrip.framework.apollo.biz.entity.Audit; import com.ctrip.framework.apollo.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.entity.Commit; import com.ctrip.framework.apollo.biz.entity.Commit;
import com.ctrip.framework.apollo.biz.entity.Item; import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.utils.ConfigChangeContentBuilder; import com.ctrip.framework.apollo.biz.utils.ConfigChangeContentBuilder;
import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets; import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
...@@ -27,6 +28,10 @@ public class ItemSetService { ...@@ -27,6 +28,10 @@ public class ItemSetService {
@Autowired @Autowired
private ItemService itemService; private ItemService itemService;
@Transactional
public ItemChangeSets updateSet(Namespace namespace, ItemChangeSets changeSets){
return updateSet(namespace.getAppId(), namespace.getClusterName(), namespace.getNamespaceName(), changeSets);
}
@Transactional @Transactional
public ItemChangeSets updateSet(String appId, String clusterName, public ItemChangeSets updateSet(String appId, String clusterName,
......
package com.ctrip.framework.apollo.biz.service;
import com.google.common.collect.Maps;
import com.ctrip.framework.apollo.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.entity.Cluster;
import com.ctrip.framework.apollo.biz.entity.GrayReleaseRule;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.repository.GrayReleaseRuleRepository;
import com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus;
import com.ctrip.framework.apollo.common.constants.ReleaseOperation;
import com.ctrip.framework.apollo.common.constants.ReleaseOperationContext;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.GrayReleaseRuleItemTransformer;
import com.ctrip.framework.apollo.common.utils.UniqueKeyGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
@Service
public class NamespaceBranchService {
@Autowired
private AuditService auditService;
@Autowired
private GrayReleaseRuleRepository grayReleaseRuleRepository;
@Autowired
private ClusterService clusterService;
@Autowired
private ReleaseService releaseService;
@Autowired
private NamespaceService namespaceService;
@Autowired
private ReleaseHistoryService releaseHistoryService;
@Transactional
public Namespace createBranch(String appId, String parentClusterName, String namespaceName, String operator){
Namespace childNamespace = findBranch(appId, parentClusterName, namespaceName);
if (childNamespace != null){
throw new BadRequestException("namespace already has branch");
}
Cluster parentCluster = clusterService.findOne(appId, parentClusterName);
if (parentCluster == null || parentCluster.getParentClusterId() != 0) {
throw new BadRequestException("cluster not exist or illegal cluster");
}
//create child cluster
Cluster childCluster = createChildCluster(appId, parentCluster, namespaceName, operator);
Cluster createdChildCluster = clusterService.saveWithoutCreatePrivateNamespace(childCluster);
//create child namespace
childNamespace = createNamespaceBranch(appId, createdChildCluster.getName(),
namespaceName, operator);
return namespaceService.save(childNamespace);
}
public Namespace findBranch(String appId, String parentClusterName, String namespaceName) {
return namespaceService.findChildNamespace(appId, parentClusterName, namespaceName);
}
public GrayReleaseRule findBranchGrayRules(String appId, String clusterName, String namespaceName,
String branchName) {
return grayReleaseRuleRepository
.findTopByAppIdAndClusterNameAndNamespaceNameAndBranchNameOrderByIdDesc(appId, clusterName, namespaceName, branchName);
}
@Transactional
public void updateBranchGrayRules(String appId, String clusterName, String namespaceName,
String branchName, GrayReleaseRule newRules) {
doUpdateBranchGrayRules(appId, clusterName, namespaceName, branchName, newRules, true, ReleaseOperation.APPLY_GRAY_RULES);
}
private void doUpdateBranchGrayRules(String appId, String clusterName, String namespaceName,
String branchName, GrayReleaseRule newRules, boolean recordReleaseHistory, int releaseOperation) {
GrayReleaseRule oldRules = grayReleaseRuleRepository
.findTopByAppIdAndClusterNameAndNamespaceNameAndBranchNameOrderByIdDesc(appId, clusterName, namespaceName, branchName);
Release latestBranchRelease = releaseService.findLatestActiveRelease(appId, branchName, namespaceName);
long latestBranchReleaseId = latestBranchRelease != null ? latestBranchRelease.getId() : 0;
newRules.setReleaseId(latestBranchReleaseId);
grayReleaseRuleRepository.save(newRules);
//delete old rules
if (oldRules != null) {
grayReleaseRuleRepository.delete(oldRules);
}
if (recordReleaseHistory) {
Map<String, Object> releaseOperationContext = Maps.newHashMap();
releaseOperationContext.put(ReleaseOperationContext.RULES, GrayReleaseRuleItemTransformer
.batchTransformFromJSON(newRules.getRules()));
if (oldRules != null) {
releaseOperationContext.put(ReleaseOperationContext.OLD_RULES,
GrayReleaseRuleItemTransformer.batchTransformFromJSON(oldRules.getRules()));
}
releaseHistoryService.createReleaseHistory(appId, clusterName, namespaceName, branchName, latestBranchReleaseId,
latestBranchReleaseId, releaseOperation, releaseOperationContext, newRules.getDataChangeLastModifiedBy());
}
}
@Transactional
public GrayReleaseRule updateRulesReleaseId(String appId, String clusterName,
String namespaceName, String branchName,
long latestReleaseId, String operator) {
GrayReleaseRule oldRules = grayReleaseRuleRepository.
findTopByAppIdAndClusterNameAndNamespaceNameAndBranchNameOrderByIdDesc(appId, clusterName, namespaceName, branchName);
if (oldRules == null) {
return null;
}
GrayReleaseRule newRules = new GrayReleaseRule();
newRules.setBranchStatus(NamespaceBranchStatus.ACTIVE);
newRules.setReleaseId(latestReleaseId);
newRules.setRules(oldRules.getRules());
newRules.setAppId(oldRules.getAppId());
newRules.setClusterName(oldRules.getClusterName());
newRules.setNamespaceName(oldRules.getNamespaceName());
newRules.setBranchName(oldRules.getBranchName());
newRules.setDataChangeCreatedBy(operator);
newRules.setDataChangeLastModifiedBy(operator);
grayReleaseRuleRepository.save(newRules);
grayReleaseRuleRepository.delete(oldRules);
return newRules;
}
@Transactional
public void deleteBranch(String appId, String clusterName, String namespaceName,
String branchName, int branchStatus, String operator) {
Cluster toDeleteCluster = clusterService.findOne(appId, branchName);
if (toDeleteCluster == null) {
return;
}
Release latestBranchRelease = releaseService.findLatestActiveRelease(appId, branchName, namespaceName);
long latestBranchReleaseId = latestBranchRelease != null ? latestBranchRelease.getId() : 0;
//update branch rules
GrayReleaseRule deleteRule = new GrayReleaseRule();
deleteRule.setRules("[]");
deleteRule.setAppId(appId);
deleteRule.setClusterName(clusterName);
deleteRule.setNamespaceName(namespaceName);
deleteRule.setBranchName(branchName);
deleteRule.setBranchStatus(branchStatus);
deleteRule.setDataChangeLastModifiedBy(operator);
deleteRule.setDataChangeCreatedBy(operator);
doUpdateBranchGrayRules(appId, clusterName, namespaceName, branchName, deleteRule, false, -1);
//delete branch cluster
clusterService.delete(toDeleteCluster.getId(), operator);
int releaseOperation = branchStatus == NamespaceBranchStatus.MERGED ? ReleaseOperation
.GRAY_RELEASE_DELETED_AFTER_MERGE : ReleaseOperation.ABANDON_GRAY_RELEASE;
releaseHistoryService.createReleaseHistory(appId, clusterName, namespaceName, branchName, latestBranchReleaseId,
latestBranchReleaseId, releaseOperation, null, operator);
auditService.audit("Branch", toDeleteCluster.getId(), Audit.OP.DELETE, operator);
}
private Cluster createChildCluster(String appId, Cluster parentCluster,
String namespaceName, String operator) {
Cluster childCluster = new Cluster();
childCluster.setAppId(appId);
childCluster.setParentClusterId(parentCluster.getId());
childCluster.setName(UniqueKeyGenerator.generate(appId, parentCluster.getName(), namespaceName));
childCluster.setDataChangeCreatedBy(operator);
childCluster.setDataChangeLastModifiedBy(operator);
return childCluster;
}
private Namespace createNamespaceBranch(String appId, String clusterName, String namespaceName, String operator) {
Namespace childNamespace = new Namespace();
childNamespace.setAppId(appId);
childNamespace.setClusterName(clusterName);
childNamespace.setNamespaceName(namespaceName);
childNamespace.setDataChangeLastModifiedBy(operator);
childNamespace.setDataChangeCreatedBy(operator);
return childNamespace;
}
}
...@@ -13,11 +13,11 @@ public class NamespaceLockService { ...@@ -13,11 +13,11 @@ public class NamespaceLockService {
@Autowired @Autowired
private NamespaceLockRepository namespaceLockRepository; private NamespaceLockRepository namespaceLockRepository;
public NamespaceLock findLock(Long namespaceId){ public NamespaceLock findLock(Long namespaceId){
return namespaceLockRepository.findByNamespaceId(namespaceId); return namespaceLockRepository.findByNamespaceId(namespaceId);
} }
@Transactional @Transactional
public NamespaceLock tryLock(NamespaceLock lock){ public NamespaceLock tryLock(NamespaceLock lock){
return namespaceLockRepository.save(lock); return namespaceLockRepository.save(lock);
......
package com.ctrip.framework.apollo.biz.service; package com.ctrip.framework.apollo.biz.service;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import com.ctrip.framework.apollo.biz.entity.Audit; import com.ctrip.framework.apollo.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.entity.Cluster;
import com.ctrip.framework.apollo.biz.entity.Namespace; import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.repository.NamespaceRepository; import com.ctrip.framework.apollo.biz.repository.NamespaceRepository;
import com.ctrip.framework.apollo.common.entity.AppNamespace; import com.ctrip.framework.apollo.common.entity.AppNamespace;
...@@ -30,7 +36,78 @@ public class NamespaceService { ...@@ -30,7 +36,78 @@ public class NamespaceService {
private CommitService commitService; private CommitService commitService;
@Autowired @Autowired
private ReleaseService releaseService; private ReleaseService releaseService;
@Autowired
private ClusterService clusterService;
public Namespace findOne(Long namespaceId) {
return namespaceRepository.findOne(namespaceId);
}
public Namespace findOne(String appId, String clusterName, String namespaceName) {
return namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, clusterName,
namespaceName);
}
public List<Namespace> findNamespaces(String appId, String clusterName) {
List<Namespace> namespaces = namespaceRepository.findByAppIdAndClusterNameOrderByIdAsc(appId, clusterName);
if (namespaces == null) {
return Collections.emptyList();
}
return namespaces;
}
public List<Namespace> findByAppIdAndNamespaceName(String appId, String namespaceName){
return namespaceRepository.findByAppIdAndNamespaceName(appId, namespaceName);
}
public Namespace findChildNamespace(String appId, String parentClusterName, String namespaceName){
List<Namespace> namespaces = findByAppIdAndNamespaceName(appId, namespaceName);
if (CollectionUtils.isEmpty(namespaces) || namespaces.size() == 1){
return null;
}
List<Cluster> childClusters = clusterService.findChildClusters(appId, parentClusterName);
if (CollectionUtils.isEmpty(childClusters)){
return null;
}
Set<String> childClusterNames = childClusters.stream().map(Cluster::getName).collect(Collectors.toSet());
//the child namespace is the intersection of the child clusters and child namespaces
for (Namespace namespace: namespaces){
if (childClusterNames.contains(namespace.getClusterName())){
return namespace;
}
}
return null;
}
public Namespace findChildNamespace(Namespace parentNamespace){
String appId = parentNamespace.getAppId();
String parentClusterName = parentNamespace.getClusterName();
String namespaceName = parentNamespace.getNamespaceName();
return findChildNamespace(appId, parentClusterName, namespaceName);
}
public Namespace findParentNamespace(Namespace namespace){
String appId = namespace.getAppId();
String namespaceName = namespace.getNamespaceName();
Cluster cluster = clusterService.findOne(appId, namespace.getClusterName());
if (cluster != null && cluster.getParentClusterId() > 0){
Cluster parentCluster = clusterService.findOne(cluster.getParentClusterId());
return findOne(appId, parentCluster.getName(), namespaceName);
}
return null;
}
public boolean isChildNamespace(Namespace namespace){
return findParentNamespace(namespace) != null;
}
public boolean isNamespaceUnique(String appId, String cluster, String namespace) { public boolean isNamespaceUnique(String appId, String cluster, String namespace) {
Objects.requireNonNull(appId, "AppId must not be null"); Objects.requireNonNull(appId, "AppId must not be null");
...@@ -59,7 +136,10 @@ public class NamespaceService { ...@@ -59,7 +136,10 @@ public class NamespaceService {
itemService.batchDelete(namespace.getId(), operator); itemService.batchDelete(namespace.getId(), operator);
commitService.batchDelete(appId, clusterName, namespace.getNamespaceName(), operator); commitService.batchDelete(appId, clusterName, namespace.getNamespaceName(), operator);
if (!isChildNamespace(namespace)){
releaseService.batchDelete(appId, clusterName, namespace.getNamespaceName(), operator); releaseService.batchDelete(appId, clusterName, namespace.getNamespaceName(), operator);
}
namespace.setDeleted(true); namespace.setDeleted(true);
namespace.setDataChangeLastModifiedBy(operator); namespace.setDataChangeLastModifiedBy(operator);
...@@ -69,23 +149,6 @@ public class NamespaceService { ...@@ -69,23 +149,6 @@ public class NamespaceService {
return namespaceRepository.save(namespace); return namespaceRepository.save(namespace);
} }
public Namespace findOne(Long namespaceId) {
return namespaceRepository.findOne(namespaceId);
}
public Namespace findOne(String appId, String clusterName, String namespaceName) {
return namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, clusterName,
namespaceName);
}
public List<Namespace> findNamespaces(String appId, String clusterName) {
List<Namespace> groups = namespaceRepository.findByAppIdAndClusterNameOrderByIdAsc(appId, clusterName);
if (groups == null) {
return Collections.emptyList();
}
return groups;
}
@Transactional @Transactional
public Namespace save(Namespace entity) { public Namespace save(Namespace entity) {
if (!isNamespaceUnique(entity.getAppId(), entity.getClusterName(), entity.getNamespaceName())) { if (!isNamespaceUnique(entity.getAppId(), entity.getClusterName(), entity.getNamespaceName())) {
...@@ -131,4 +194,6 @@ public class NamespaceService { ...@@ -131,4 +194,6 @@ public class NamespaceService {
} }
} }
} }
package com.ctrip.framework.apollo.biz.service;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.entity.ReleaseHistory;
import com.ctrip.framework.apollo.biz.repository.ReleaseHistoryRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@Service
public class ReleaseHistoryService {
@Autowired
private ReleaseHistoryRepository releaseHistoryRepository;
@Autowired
private ReleaseService releaseService;
@Autowired
private AuditService auditService;
private Gson gson = new Gson();
public Page<ReleaseHistory> findReleaseHistoriesByNamespace(String appId, String clusterName,
String namespaceName, Pageable
pageable) {
return releaseHistoryRepository.findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(appId, clusterName,
namespaceName, pageable);
}
public List<ReleaseHistory> findReleaseHistoriesByReleaseId(long releaseId) {
return releaseHistoryRepository.findByReleaseId(releaseId);
}
@Transactional
public ReleaseHistory createReleaseHistory(String appId, String clusterName, String
namespaceName, String branchName, long releaseId, long previousReleaseId, int operation,
Map<String, Object> operationContext, String operator) {
ReleaseHistory releaseHistory = new ReleaseHistory();
releaseHistory.setAppId(appId);
releaseHistory.setClusterName(clusterName);
releaseHistory.setNamespaceName(namespaceName);
releaseHistory.setBranchName(branchName);
releaseHistory.setReleaseId(releaseId);
releaseHistory.setPreviousReleaseId(previousReleaseId);
releaseHistory.setOperation(operation);
if (operationContext == null) {
releaseHistory.setOperationContext("{}"); //default empty object
} else {
releaseHistory.setOperationContext(gson.toJson(operationContext));
}
releaseHistory.setDataChangeCreatedTime(new Date());
releaseHistory.setDataChangeCreatedBy(operator);
releaseHistory.setDataChangeLastModifiedBy(operator);
releaseHistoryRepository.save(releaseHistory);
auditService.audit(ReleaseHistory.class.getSimpleName(), releaseHistory.getId(),
Audit.OP.INSERT, releaseHistory.getDataChangeCreatedBy());
return releaseHistory;
}
}
package com.ctrip.framework.apollo.biz.service; package com.ctrip.framework.apollo.biz.service;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.biz.entity.Audit; import com.ctrip.framework.apollo.biz.entity.Audit;
import com.ctrip.framework.apollo.biz.entity.Instance; import com.ctrip.framework.apollo.biz.entity.GrayReleaseRule;
import com.ctrip.framework.apollo.biz.entity.Item; import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace; import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.NamespaceLock; import com.ctrip.framework.apollo.biz.entity.NamespaceLock;
import com.ctrip.framework.apollo.biz.entity.Release; import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.repository.ItemRepository;
import com.ctrip.framework.apollo.biz.repository.ReleaseRepository; import com.ctrip.framework.apollo.biz.repository.ReleaseRepository;
import com.ctrip.framework.apollo.biz.utils.ReleaseKeyGenerator; import com.ctrip.framework.apollo.biz.utils.ReleaseKeyGenerator;
import com.ctrip.framework.apollo.common.constants.ReleaseOperation;
import com.ctrip.framework.apollo.common.constants.ReleaseOperationContext;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException; import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.GrayReleaseRuleItemTransformer;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.apache.commons.collections.collection.UnmodifiableCollection; import org.apache.commons.lang.time.FastDateFormat;
import org.apache.commons.collections.list.UnmodifiableList;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.lang.reflect.Type;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
/** /**
...@@ -36,21 +43,36 @@ import java.util.Set; ...@@ -36,21 +43,36 @@ import java.util.Set;
*/ */
@Service @Service
public class ReleaseService { public class ReleaseService {
private static final FastDateFormat TIMESTAMP_FORMAT = FastDateFormat.getInstance("yyyyMMddHHmmss");
private Gson gson = new Gson(); private Gson gson = new Gson();
private Type configurationTypeReference = new TypeToken<Map<String, String>>() {
}.getType();
@Autowired @Autowired
private ReleaseRepository releaseRepository; private ReleaseRepository releaseRepository;
@Autowired @Autowired
private ItemRepository itemRepository; private ItemService itemService;
@Autowired @Autowired
private AuditService auditService; private AuditService auditService;
@Autowired @Autowired
private NamespaceLockService namespaceLockService; private NamespaceLockService namespaceLockService;
@Autowired
private NamespaceService namespaceService;
@Autowired
private NamespaceBranchService namespaceBranchService;
@Autowired
private ReleaseHistoryService releaseHistoryService;
@Autowired
private ItemSetService itemSetService;
public Release findOne(long releaseId) { public Release findOne(long releaseId) {
Release release = releaseRepository.findOne(releaseId); return releaseRepository.findOne(releaseId);
return release; }
public Release findActiveOne(long releaseId) {
return releaseRepository.findByIdAndIsAbandonedFalse(releaseId);
} }
public List<Release> findByReleaseIds(Set<Long> releaseIds) { public List<Release> findByReleaseIds(Set<Long> releaseIds) {
...@@ -65,10 +87,16 @@ public class ReleaseService { ...@@ -65,10 +87,16 @@ public class ReleaseService {
return releaseRepository.findByReleaseKeyIn(releaseKeys); return releaseRepository.findByReleaseKeyIn(releaseKeys);
} }
public Release findLatestActiveRelease(Namespace namespace) {
return findLatestActiveRelease(namespace.getAppId(),
namespace.getClusterName(), namespace.getNamespaceName());
}
public Release findLatestActiveRelease(String appId, String clusterName, String namespaceName) { public Release findLatestActiveRelease(String appId, String clusterName, String namespaceName) {
Release release = releaseRepository.findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc( return releaseRepository.findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(appId,
appId, clusterName, namespaceName); clusterName,
return release; namespaceName);
} }
public List<Release> findAllReleases(String appId, String clusterName, String namespaceName, Pageable page) { public List<Release> findAllReleases(String appId, String clusterName, String namespaceName, Pageable page) {
...@@ -85,8 +113,7 @@ public class ReleaseService { ...@@ -85,8 +113,7 @@ public class ReleaseService {
public List<Release> findActiveReleases(String appId, String clusterName, String namespaceName, Pageable page) { public List<Release> findActiveReleases(String appId, String clusterName, String namespaceName, Pageable page) {
List<Release> List<Release>
releases = releases =
releaseRepository.findByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(appId, releaseRepository.findByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(appId, clusterName,
clusterName,
namespaceName, namespaceName,
page); page);
if (releases == null) { if (releases == null) {
...@@ -96,13 +123,183 @@ public class ReleaseService { ...@@ -96,13 +123,183 @@ public class ReleaseService {
} }
@Transactional @Transactional
public Release buildRelease(String name, String comment, Namespace namespace, String operator) { public Release mergeBranchChangeSetsAndRelease(Namespace namespace, String branchName, String
releaseName, String releaseComment, ItemChangeSets changeSets) {
NamespaceLock lock = namespaceLockService.findLock(namespace.getId());
if (lock != null && lock.getDataChangeCreatedBy().equals(changeSets.getDataChangeLastModifiedBy())) {
throw new BadRequestException("config can not be published by yourself.");
}
itemSetService.updateSet(namespace, changeSets);
Release branchRelease = findLatestActiveRelease(namespace.getAppId(), branchName, namespace
.getNamespaceName());
long branchReleaseId = branchRelease == null ? 0 : branchRelease.getId();
Map<String, String> operateNamespaceItems = getNamespaceItems(namespace);
Map<String, Object> operationContext = Maps.newHashMap();
operationContext.put(ReleaseOperationContext.SOURCE_BRANCH, branchName);
operationContext.put(ReleaseOperationContext.BASE_RELEASE_ID, branchReleaseId);
Release release = masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems,
changeSets.getDataChangeLastModifiedBy(),
ReleaseOperation.GRAY_RELEASE_MERGE_TO_MASTER, operationContext);
return release;
}
@Transactional
public Release publish(Namespace namespace, String releaseName, String releaseComment,
String operator) {
NamespaceLock lock = namespaceLockService.findLock(namespace.getId()); NamespaceLock lock = namespaceLockService.findLock(namespace.getId());
if (lock != null && lock.getDataChangeCreatedBy().equals(operator)) { if (lock != null && lock.getDataChangeCreatedBy().equals(operator)) {
throw new BadRequestException(String.format("Current editor %s is not allowed to release the config", operator)); throw new BadRequestException("config can not be published by yourself.");
}
Map<String, String> operateNamespaceItems = getNamespaceItems(namespace);
Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
//branch release
if (parentNamespace != null) {
return publishBranchNamespace(parentNamespace, namespace, operateNamespaceItems,
releaseName, releaseComment, operator);
}
Namespace childNamespace = namespaceService.findChildNamespace(namespace);
Release previousRelease = null;
if (childNamespace != null) {
previousRelease = findLatestActiveRelease(namespace);
}
//master release
Release release = masterRelease(namespace, releaseName, releaseComment, operateNamespaceItems,
operator, ReleaseOperation.NORMAL_RELEASE, null);
//merge to branch and auto release
if (childNamespace != null) {
mergeFromMasterAndPublishBranch(namespace, childNamespace, operateNamespaceItems,
releaseName, releaseComment, operator, previousRelease, release);
}
return release;
}
private void mergeFromMasterAndPublishBranch(Namespace parentNamespace, Namespace childNamespace,
Map<String, String> parentNamespaceItems,
String releaseName, String releaseComment,
String operator, Release masterPreviousRelease, Release parentRelease) {
// //create release for child namespace
Map<String, String> childReleaseConfiguration = getNamespaceReleaseConfiguration(childNamespace);
Map<String, String> parentNamespaceOldConfiguration = masterPreviousRelease == null ? null :
gson.fromJson(
masterPreviousRelease.getConfigurations(),
configurationTypeReference);
Map<String, String>
childNamespaceToPublishConfigs =
calculateChildNamespaceToPublishConfiguration(parentNamespaceOldConfiguration,
parentNamespaceItems,
childNamespace);
//compare
if (!childNamespaceToPublishConfigs.equals(childReleaseConfiguration)) {
branchRelease(parentNamespace, childNamespace, releaseName, releaseComment,
childNamespaceToPublishConfigs, parentRelease.getId(), operator,
ReleaseOperation.MASTER_NORMAL_RELEASE_MERGE_TO_GRAY);
}
}
private Release publishBranchNamespace(Namespace parentNamespace, Namespace childNamespace,
Map<String, String> childNamespaceItems,
String releaseName, String releaseComment, String operator) {
Release parentLatestRelease = findLatestActiveRelease(parentNamespace);
Map<String, String> parentConfigurations = parentLatestRelease != null ?
gson.fromJson(parentLatestRelease.getConfigurations(),
configurationTypeReference) : new HashMap<>();
Map<String, String> childNamespaceToPublishConfigs = mergeConfiguration(parentConfigurations, childNamespaceItems);
Release release =
branchRelease(parentNamespace, childNamespace, releaseName, releaseComment,
childNamespaceToPublishConfigs, parentLatestRelease.getId(), operator,
ReleaseOperation.GRAY_RELEASE);
return release;
}
private Release masterRelease(Namespace namespace, String releaseName, String releaseComment,
Map<String, String> configurations, String operator,
int releaseOperation, Map<String, Object> operationContext) {
Release lastActiveRelease = findLatestActiveRelease(namespace);
long previousReleaseId = lastActiveRelease == null ? 0 : lastActiveRelease.getId();
Release release = createRelease(namespace, releaseName, releaseComment,
configurations, operator);
releaseHistoryService.createReleaseHistory(namespace.getAppId(), namespace.getClusterName(),
namespace.getNamespaceName(), namespace.getClusterName(),
release.getId(),
previousReleaseId, releaseOperation, operationContext, operator);
return release;
}
private Release branchRelease(Namespace parentNamespace, Namespace childNamespace,
String releaseName, String releaseComment,
Map<String, String> configurations, long baseReleaseId,
String operator, int releaseOperation) {
Release previousRelease = findLatestActiveRelease(childNamespace.getAppId(),
childNamespace.getClusterName(),
childNamespace.getNamespaceName());
long previousReleaseId = previousRelease == null ? 0 : previousRelease.getId();
Map<String, Object> releaseOperationContext = Maps.newHashMap();
releaseOperationContext.put(ReleaseOperationContext.BASE_RELEASE_ID, baseReleaseId);
Release release =
createRelease(childNamespace, releaseName, releaseComment, configurations, operator);
//update gray release rules
GrayReleaseRule grayReleaseRule = namespaceBranchService.updateRulesReleaseId(childNamespace.getAppId(),
parentNamespace.getClusterName(),
childNamespace.getNamespaceName(),
childNamespace.getClusterName(),
release.getId(), operator);
if (grayReleaseRule != null) {
releaseOperationContext.put(ReleaseOperationContext.RULES, GrayReleaseRuleItemTransformer
.batchTransformFromJSON(grayReleaseRule.getRules()));
}
releaseHistoryService.createReleaseHistory(parentNamespace.getAppId(), parentNamespace.getClusterName(),
parentNamespace.getNamespaceName(), childNamespace.getClusterName(),
release.getId(),
previousReleaseId, releaseOperation, releaseOperationContext, operator);
return release;
}
private Map<String, String> mergeConfiguration(Map<String, String> baseConfigurations,
Map<String, String> coverConfigurations) {
Map<String, String> result = new HashMap<>();
//copy base configuration
for (Map.Entry<String, String> entry : baseConfigurations.entrySet()) {
result.put(entry.getKey(), entry.getValue());
} }
List<Item> items = itemRepository.findByNamespaceIdOrderByLineNumAsc(namespace.getId()); //update and publish
for (Map.Entry<String, String> entry : coverConfigurations.entrySet()) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
private Map<String, String> getNamespaceItems(Namespace namespace) {
List<Item> items = itemService.findItems(namespace.getId());
Map<String, String> configurations = new HashMap<String, String>(); Map<String, String> configurations = new HashMap<String, String>();
for (Item item : items) { for (Item item : items) {
if (StringUtils.isEmpty(item.getKey())) { if (StringUtils.isEmpty(item.getKey())) {
...@@ -111,6 +308,21 @@ public class ReleaseService { ...@@ -111,6 +308,21 @@ public class ReleaseService {
configurations.put(item.getKey(), item.getValue()); configurations.put(item.getKey(), item.getValue());
} }
return configurations;
}
private Map<String, String> getNamespaceReleaseConfiguration(Namespace namespace) {
Release release = findLatestActiveRelease(namespace);
Map<String, String> configuration = new HashMap<>();
if (release != null) {
configuration = new Gson().fromJson(release.getConfigurations(),
configurationTypeReference);
}
return configuration;
}
private Release createRelease(Namespace namespace, String name, String comment,
Map<String, String> configurations, String operator) {
Release release = new Release(); Release release = new Release();
release.setReleaseKey(ReleaseKeyGenerator.generateReleaseKey(namespace)); release.setReleaseKey(ReleaseKeyGenerator.generateReleaseKey(namespace));
release.setDataChangeCreatedTime(new Date()); release.setDataChangeCreatedTime(new Date());
...@@ -134,10 +346,10 @@ public class ReleaseService { ...@@ -134,10 +346,10 @@ public class ReleaseService {
@Transactional @Transactional
public Release rollback(long releaseId, String operator) { public Release rollback(long releaseId, String operator) {
Release release = findOne(releaseId); Release release = findOne(releaseId);
if (release == null){ if (release == null) {
throw new NotFoundException("release not found"); throw new NotFoundException("release not found");
} }
if (release.isAbandoned()){ if (release.isAbandoned()) {
throw new BadRequestException("release is not active"); throw new BadRequestException("release is not active");
} }
...@@ -149,7 +361,8 @@ public class ReleaseService { ...@@ -149,7 +361,8 @@ public class ReleaseService {
List<Release> twoLatestActiveReleases = findActiveReleases(appId, clusterName, namespaceName, page); List<Release> twoLatestActiveReleases = findActiveReleases(appId, clusterName, namespaceName, page);
if (twoLatestActiveReleases == null || twoLatestActiveReleases.size() < 2) { if (twoLatestActiveReleases == null || twoLatestActiveReleases.size() < 2) {
throw new BadRequestException(String.format( throw new BadRequestException(String.format(
"Can't rollback namespace(appId=%s, clusterName=%s, namespaceName=%s) because there is only one active release", appId, "Can't rollback namespace(appId=%s, clusterName=%s, namespaceName=%s) because there is only one active release",
appId,
clusterName, clusterName,
namespaceName)); namespaceName));
} }
...@@ -157,11 +370,95 @@ public class ReleaseService { ...@@ -157,11 +370,95 @@ public class ReleaseService {
release.setAbandoned(true); release.setAbandoned(true);
release.setDataChangeLastModifiedBy(operator); release.setDataChangeLastModifiedBy(operator);
return releaseRepository.save(release); releaseRepository.save(release);
releaseHistoryService.createReleaseHistory(appId, clusterName,
namespaceName, clusterName, twoLatestActiveReleases.get(1).getId(),
release.getId(), ReleaseOperation.ROLLBACK, null, operator);
//publish child namespace if namespace has child
rollbackChildNamespace(appId, clusterName, namespaceName, twoLatestActiveReleases, operator);
return release;
}
private void rollbackChildNamespace(String appId, String clusterName, String namespaceName,
List<Release> parentNamespaceTwoLatestActiveRelease, String operator) {
Namespace parentNamespace = namespaceService.findOne(appId, clusterName, namespaceName);
Namespace childNamespace = namespaceService.findChildNamespace(appId, clusterName, namespaceName);
if (parentNamespace == null || childNamespace == null) {
return;
}
Release abandonedRelease = parentNamespaceTwoLatestActiveRelease.get(0);
Release parentNamespaceNewLatestRelease = parentNamespaceTwoLatestActiveRelease.get(1);
Map<String, String> parentNamespaceAbandonedConfiguration = gson.fromJson(abandonedRelease.getConfigurations(),
configurationTypeReference);
Map<String, String>
parentNamespaceNewLatestConfiguration =
gson.fromJson(parentNamespaceNewLatestRelease.getConfigurations(),
configurationTypeReference);
Map<String, String>
childNamespaceNewConfiguration =
calculateChildNamespaceToPublishConfiguration(parentNamespaceAbandonedConfiguration,
parentNamespaceNewLatestConfiguration,
childNamespace);
branchRelease(parentNamespace, childNamespace, TIMESTAMP_FORMAT.format(new Date()) + "-master-rollback-merge-to-gray", "",
childNamespaceNewConfiguration, parentNamespaceNewLatestRelease.getId(), operator,
ReleaseOperation.MATER_ROLLBACK_MERGE_TO_GRAY);
}
private Map<String, String> calculateChildNamespaceToPublishConfiguration(
Map<String, String> parentNamespaceOldConfiguration,
Map<String, String> parentNamespaceNewConfiguration,
Namespace childNamespace) {
//first. calculate child namespace modified configs
Release childNamespaceLatestActiveRelease = findLatestActiveRelease(childNamespace);
Map<String, String> childNamespaceLatestActiveConfiguration = childNamespaceLatestActiveRelease == null ? null :
gson.fromJson(childNamespaceLatestActiveRelease
.getConfigurations(),
configurationTypeReference);
Map<String, String> childNamespaceModifiedConfiguration = calculateBranchModifiedItemsAccordingToRelease(
parentNamespaceOldConfiguration, childNamespaceLatestActiveConfiguration);
//second. append child namespace modified configs to parent namespace new latest configuration
return mergeConfiguration(parentNamespaceNewConfiguration, childNamespaceModifiedConfiguration);
}
private Map<String, String> calculateBranchModifiedItemsAccordingToRelease(
Map<String, String> masterReleaseConfigs,
Map<String, String> branchReleaseConfigs) {
Map<String, String> modifiedConfigs = new HashMap<>();
if (CollectionUtils.isEmpty(branchReleaseConfigs)) {
return modifiedConfigs;
}
if (CollectionUtils.isEmpty(masterReleaseConfigs)) {
return branchReleaseConfigs;
}
for (Map.Entry<String, String> entry : branchReleaseConfigs.entrySet()) {
if (!Objects.equals(entry.getValue(), masterReleaseConfigs.get(entry.getKey()))) {
modifiedConfigs.put(entry.getKey(), entry.getValue());
}
}
return modifiedConfigs;
} }
@Transactional @Transactional
public int batchDelete(String appId, String clusterName, String namespaceName, String operator){ public int batchDelete(String appId, String clusterName, String namespaceName, String operator) {
return releaseRepository.batchDelete(appId, clusterName, namespaceName, operator); return releaseRepository.batchDelete(appId, clusterName, namespaceName, operator);
} }
} }
package com.ctrip.framework.apollo.biz.utils; package com.ctrip.framework.apollo.biz.utils;
import com.google.common.base.Joiner;
import com.ctrip.framework.apollo.biz.entity.Namespace; import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.core.utils.ByteUtil; import com.ctrip.framework.apollo.common.utils.UniqueKeyGenerator;
import com.ctrip.framework.apollo.core.utils.MachineUtil;
import org.apache.commons.lang.time.FastDateFormat;
import java.security.SecureRandom;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/** /**
* @author Jason Song(song_s@ctrip.com) * @author Jason Song(song_s@ctrip.com)
*/ */
public class ReleaseKeyGenerator { public class ReleaseKeyGenerator extends UniqueKeyGenerator {
private static final FastDateFormat TIMESTAMP_FORMAT = FastDateFormat.getInstance("yyyyMMddHHmmss");
private static final AtomicInteger releaseCounter = new AtomicInteger(new SecureRandom().nextInt());
private static final Joiner KEY_JOINER = Joiner.on("-");
/** /**
* Generate the release key in the format: timestamp+appId+cluster+namespace+hash(ipAsInt+counter) * Generate the release key in the format: timestamp+appId+cluster+namespace+hash(ipAsInt+counter)
...@@ -30,29 +17,6 @@ public class ReleaseKeyGenerator { ...@@ -30,29 +17,6 @@ public class ReleaseKeyGenerator {
* @return the unique release key * @return the unique release key
*/ */
public static String generateReleaseKey(Namespace namespace) { public static String generateReleaseKey(Namespace namespace) {
String hexIdString = return generate(namespace.getAppId(), namespace.getClusterName(), namespace.getNamespaceName());
ByteUtil.toHexString(
toByteArray(Objects.hash(namespace.getAppId(), namespace.getClusterName(),
namespace.getNamespaceName()), MachineUtil.getMachineIdentifier(),
releaseCounter.incrementAndGet()));
return KEY_JOINER.join(TIMESTAMP_FORMAT.format(new Date()), hexIdString);
}
/**
* Concat machine id, counter and key to byte array
* Only retrieve lower 3 bytes of the id and counter and 2 bytes of the keyHashCode
*/
private static byte[] toByteArray(int keyHashCode, int machineIdentifier, int counter) {
byte[] bytes = new byte[8];
bytes[0] = ByteUtil.int1(keyHashCode);
bytes[1] = ByteUtil.int0(keyHashCode);
bytes[2] = ByteUtil.int2(machineIdentifier);
bytes[3] = ByteUtil.int1(machineIdentifier);
bytes[4] = ByteUtil.int0(machineIdentifier);
bytes[5] = ByteUtil.int2(counter);
bytes[6] = ByteUtil.int1(counter);
bytes[7] = ByteUtil.int0(counter);
return bytes;
} }
} }
package com.ctrip.framework.apollo.biz.utils;
import com.google.common.base.Joiner;
import com.ctrip.framework.apollo.core.ConfigConsts;
public class ReleaseMessageKeyGenerator {
private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
public static String generate(String appId, String cluster, String namespace) {
return STRING_JOINER.join(appId, cluster, namespace);
}
}
package com.ctrip.framework.apollo.biz; package com.ctrip.framework.apollo.biz;
import com.ctrip.framework.apollo.biz.eureka.ApolloEurekaClientConfigTest; import com.ctrip.framework.apollo.biz.eureka.ApolloEurekaClientConfigTest;
import com.ctrip.framework.apollo.biz.grayReleaseRule.GrayReleaseRulesHolderTest;
import com.ctrip.framework.apollo.biz.message.DatabaseMessageSenderTest; import com.ctrip.framework.apollo.biz.message.DatabaseMessageSenderTest;
import com.ctrip.framework.apollo.biz.message.ReleaseMessageScannerTest; import com.ctrip.framework.apollo.biz.message.ReleaseMessageScannerTest;
import com.ctrip.framework.apollo.biz.repository.AppNamespaceRepositoryTest; import com.ctrip.framework.apollo.biz.repository.AppNamespaceRepositoryTest;
...@@ -9,7 +10,9 @@ import com.ctrip.framework.apollo.biz.service.AdminServiceTest; ...@@ -9,7 +10,9 @@ import com.ctrip.framework.apollo.biz.service.AdminServiceTest;
import com.ctrip.framework.apollo.biz.service.AdminServiceTransactionTest; import com.ctrip.framework.apollo.biz.service.AdminServiceTransactionTest;
import com.ctrip.framework.apollo.biz.service.ClusterServiceTest; import com.ctrip.framework.apollo.biz.service.ClusterServiceTest;
import com.ctrip.framework.apollo.biz.service.InstanceServiceTest; import com.ctrip.framework.apollo.biz.service.InstanceServiceTest;
import com.ctrip.framework.apollo.biz.service.NamespaceBranchServiceTest;
import com.ctrip.framework.apollo.biz.service.PrivilegeServiceTest; import com.ctrip.framework.apollo.biz.service.PrivilegeServiceTest;
import com.ctrip.framework.apollo.biz.service.ReleaseCreationTest;
import com.ctrip.framework.apollo.biz.service.ReleaseServiceTest; import com.ctrip.framework.apollo.biz.service.ReleaseServiceTest;
import com.ctrip.framework.apollo.biz.service.ServerConfigServiceTest; import com.ctrip.framework.apollo.biz.service.ServerConfigServiceTest;
import com.ctrip.framework.apollo.biz.utils.ReleaseKeyGeneratorTest; import com.ctrip.framework.apollo.biz.utils.ReleaseKeyGeneratorTest;
...@@ -32,7 +35,10 @@ import org.junit.runners.Suite.SuiteClasses; ...@@ -32,7 +35,10 @@ import org.junit.runners.Suite.SuiteClasses;
ReleaseMessageScannerTest.class, ReleaseMessageScannerTest.class,
ClusterServiceTest.class, ClusterServiceTest.class,
ReleaseKeyGeneratorTest.class, ReleaseKeyGeneratorTest.class,
InstanceServiceTest.class InstanceServiceTest.class,
GrayReleaseRulesHolderTest.class,
NamespaceBranchServiceTest.class,
ReleaseCreationTest.class
}) })
public class AllTests { public class AllTests {
......
package com.ctrip.framework.apollo.biz.grayReleaseRule;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.biz.entity.GrayReleaseRule;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.repository.GrayReleaseRuleRepository;
import com.ctrip.framework.apollo.biz.service.ServerConfigService;
import com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleItemDTO;
import com.ctrip.framework.apollo.core.ConfigConsts;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
/**
* @author Jason Song(song_s@ctrip.com)
*/
@RunWith(MockitoJUnitRunner.class)
public class GrayReleaseRulesHolderTest {
private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
private GrayReleaseRulesHolder grayReleaseRulesHolder;
@Mock
private ServerConfigService serverConfigService;
@Mock
private GrayReleaseRuleRepository grayReleaseRuleRepository;
private Gson gson = new Gson();
private AtomicLong idCounter;
@Before
public void setUp() throws Exception {
grayReleaseRulesHolder = spy(new GrayReleaseRulesHolder());
ReflectionTestUtils.setField(grayReleaseRulesHolder, "serverConfigService",
serverConfigService);
ReflectionTestUtils.setField(grayReleaseRulesHolder, "grayReleaseRuleRepository",
grayReleaseRuleRepository);
idCounter = new AtomicLong();
}
@Test
public void testScanGrayReleaseRules() throws Exception {
String someAppId = "someAppId";
String someClusterName = "someClusterName";
String someNamespaceName = "someNamespaceName";
String anotherNamespaceName = "anotherNamespaceName";
Long someReleaseId = 1L;
int activeBranchStatus = NamespaceBranchStatus.ACTIVE;
String someClientAppId = "clientAppId1";
String someClientIp = "1.1.1.1";
String anotherClientAppId = "clientAppId2";
String anotherClientIp = "2.2.2.2";
GrayReleaseRule someRule = assembleGrayReleaseRule(someAppId, someClusterName,
someNamespaceName, Lists.newArrayList(assembleRuleItem(someClientAppId, Sets.newHashSet
(someClientIp))), someReleaseId, activeBranchStatus);
when(grayReleaseRuleRepository.findFirst500ByIdGreaterThanOrderByIdAsc(0L)).thenReturn(Lists
.newArrayList(someRule));
//scan rules
grayReleaseRulesHolder.afterPropertiesSet();
assertEquals(someReleaseId, grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule
(someClientAppId, someClientIp, someAppId, someClusterName, someNamespaceName));
assertNull(grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule(someClientAppId,
anotherClientIp, someAppId, someClusterName, someNamespaceName));
assertNull(grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule(anotherClientAppId,
someClientIp, someAppId, someClusterName, someNamespaceName));
assertNull(grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule(anotherClientAppId,
anotherClientIp, someAppId, someClusterName, someNamespaceName));
assertTrue(grayReleaseRulesHolder.hasGrayReleaseRule(someClientAppId, someClientIp,
someNamespaceName));
assertFalse(grayReleaseRulesHolder.hasGrayReleaseRule(someClientAppId, anotherClientIp,
someNamespaceName));
assertFalse(grayReleaseRulesHolder.hasGrayReleaseRule(someClientAppId, someClientIp,
anotherNamespaceName));
assertFalse(grayReleaseRulesHolder.hasGrayReleaseRule(anotherClientAppId, anotherClientIp,
someNamespaceName));
assertFalse(grayReleaseRulesHolder.hasGrayReleaseRule(anotherClientAppId, anotherClientIp,
anotherNamespaceName));
GrayReleaseRule anotherRule = assembleGrayReleaseRule(someAppId, someClusterName,
someNamespaceName, Lists.newArrayList(assembleRuleItem(anotherClientAppId, Sets.newHashSet
(anotherClientIp))), someReleaseId, activeBranchStatus);
when(grayReleaseRuleRepository.findByAppIdAndClusterNameAndNamespaceName(someAppId,
someClusterName, someNamespaceName)).thenReturn(Lists.newArrayList(anotherRule));
//send message
grayReleaseRulesHolder.handleMessage(assembleReleaseMessage(someAppId, someClusterName,
someNamespaceName), Topics.APOLLO_RELEASE_TOPIC);
assertNull(grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule
(someClientAppId, someClientIp, someAppId, someClusterName, someNamespaceName));
assertEquals(someReleaseId, grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule
(anotherClientAppId, anotherClientIp, someAppId, someClusterName, someNamespaceName));
assertFalse(grayReleaseRulesHolder.hasGrayReleaseRule(someClientAppId, someClientIp,
someNamespaceName));
assertFalse(grayReleaseRulesHolder.hasGrayReleaseRule(someClientAppId, someClientIp,
anotherNamespaceName));
assertTrue(grayReleaseRulesHolder.hasGrayReleaseRule(anotherClientAppId, anotherClientIp,
someNamespaceName));
assertFalse(grayReleaseRulesHolder.hasGrayReleaseRule(anotherClientAppId, someClientIp,
someNamespaceName));
assertFalse(grayReleaseRulesHolder.hasGrayReleaseRule(anotherClientAppId, anotherClientIp,
anotherNamespaceName));
}
private GrayReleaseRule assembleGrayReleaseRule(String appId, String clusterName, String
namespaceName, List<GrayReleaseRuleItemDTO> ruleItems, long releaseId, int branchStatus) {
GrayReleaseRule rule = new GrayReleaseRule();
rule.setId(idCounter.incrementAndGet());
rule.setAppId(appId);
rule.setClusterName(clusterName);
rule.setNamespaceName(namespaceName);
rule.setBranchName("someBranch");
rule.setRules(gson.toJson(ruleItems));
rule.setReleaseId(releaseId);
rule.setBranchStatus(branchStatus);
return rule;
}
private GrayReleaseRuleItemDTO assembleRuleItem(String clientAppId, Set<String> clientIpList) {
return new GrayReleaseRuleItemDTO(clientAppId, clientIpList);
}
private ReleaseMessage assembleReleaseMessage(String appId, String clusterName, String
namespaceName) {
String message = STRING_JOINER.join(appId, clusterName, namespaceName);
ReleaseMessage releaseMessage = new ReleaseMessage(message);
return releaseMessage;
}
}
...@@ -28,7 +28,7 @@ public class AdminServiceTest extends AbstractIntegrationTest{ ...@@ -28,7 +28,7 @@ public class AdminServiceTest extends AbstractIntegrationTest{
private AppRepository appRepository; private AppRepository appRepository;
@Autowired @Autowired
private ClusterService clsuterService; private ClusterService clusterService;
@Autowired @Autowired
private NamespaceService namespaceService; private NamespaceService namespaceService;
...@@ -49,7 +49,7 @@ public class AdminServiceTest extends AbstractIntegrationTest{ ...@@ -49,7 +49,7 @@ public class AdminServiceTest extends AbstractIntegrationTest{
app = adminService.createNewApp(app); app = adminService.createNewApp(app);
Assert.assertEquals(appId, app.getAppId()); Assert.assertEquals(appId, app.getAppId());
List<Cluster> clusters = clsuterService.findClusters(app.getAppId()); List<Cluster> clusters = clusterService.findParentClusters(app.getAppId());
Assert.assertEquals(1, clusters.size()); Assert.assertEquals(1, clusters.size());
Assert.assertEquals(ConfigConsts.CLUSTER_NAME_DEFAULT, clusters.get(0).getName()); Assert.assertEquals(ConfigConsts.CLUSTER_NAME_DEFAULT, clusters.get(0).getName());
......
...@@ -86,7 +86,7 @@ public class InstanceServiceTest extends AbstractIntegrationTest { ...@@ -86,7 +86,7 @@ public class InstanceServiceTest extends AbstractIntegrationTest {
String anotherReleaseKey = "anotherReleaseKey"; String anotherReleaseKey = "anotherReleaseKey";
InstanceConfig instanceConfig = instanceService.findInstanceConfig(someInstanceId, InstanceConfig instanceConfig = instanceService.findInstanceConfig(someInstanceId,
someConfigAppId, someConfigClusterName, someConfigNamespaceName); someConfigAppId, someConfigNamespaceName);
assertNull(instanceConfig); assertNull(instanceConfig);
...@@ -94,7 +94,7 @@ public class InstanceServiceTest extends AbstractIntegrationTest { ...@@ -94,7 +94,7 @@ public class InstanceServiceTest extends AbstractIntegrationTest {
someConfigClusterName, someConfigNamespaceName, someReleaseKey)); someConfigClusterName, someConfigNamespaceName, someReleaseKey));
instanceConfig = instanceService.findInstanceConfig(someInstanceId, someConfigAppId, instanceConfig = instanceService.findInstanceConfig(someInstanceId, someConfigAppId,
someConfigClusterName, someConfigNamespaceName); someConfigNamespaceName);
assertNotEquals(0, instanceConfig.getId()); assertNotEquals(0, instanceConfig.getId());
assertEquals(someReleaseKey, instanceConfig.getReleaseKey()); assertEquals(someReleaseKey, instanceConfig.getReleaseKey());
...@@ -104,7 +104,7 @@ public class InstanceServiceTest extends AbstractIntegrationTest { ...@@ -104,7 +104,7 @@ public class InstanceServiceTest extends AbstractIntegrationTest {
instanceService.updateInstanceConfig(instanceConfig); instanceService.updateInstanceConfig(instanceConfig);
InstanceConfig updated = instanceService.findInstanceConfig(someInstanceId, someConfigAppId, InstanceConfig updated = instanceService.findInstanceConfig(someInstanceId, someConfigAppId,
someConfigClusterName, someConfigNamespaceName); someConfigNamespaceName);
assertEquals(instanceConfig.getId(), updated.getId()); assertEquals(instanceConfig.getId(), updated.getId());
assertEquals(anotherReleaseKey, updated.getReleaseKey()); assertEquals(anotherReleaseKey, updated.getReleaseKey());
...@@ -170,6 +170,42 @@ public class InstanceServiceTest extends AbstractIntegrationTest { ...@@ -170,6 +170,42 @@ public class InstanceServiceTest extends AbstractIntegrationTest {
assertEquals(Lists.newArrayList(someInstance, anotherInstance), result.getContent()); assertEquals(Lists.newArrayList(someInstance, anotherInstance), result.getContent());
} }
@Test
@Rollback
public void testFindInstancesByNamespaceAndInstanceAppId() throws Exception {
String someConfigAppId = "someConfigAppId";
String someConfigClusterName = "someConfigClusterName";
String someConfigNamespaceName = "someConfigNamespaceName";
String someReleaseKey = "someReleaseKey";
Date someValidDate = new Date();
String someAppId = "someAppId";
String anotherAppId = "anotherAppId";
String someClusterName = "someClusterName";
String someDataCenter = "someDataCenter";
String someIp = "someIp";
Instance someInstance = instanceService.createInstance(assembleInstance(someAppId,
someClusterName, someDataCenter, someIp));
Instance anotherInstance = instanceService.createInstance(assembleInstance(anotherAppId,
someClusterName, someDataCenter, someIp));
prepareInstanceConfigForInstance(someInstance.getId(), someConfigAppId, someConfigClusterName,
someConfigNamespaceName, someReleaseKey, someValidDate);
prepareInstanceConfigForInstance(anotherInstance.getId(), someConfigAppId,
someConfigClusterName,
someConfigNamespaceName, someReleaseKey, someValidDate);
Page<Instance> result = instanceService.findInstancesByNamespaceAndInstanceAppId(someAppId,
someConfigAppId, someConfigClusterName, someConfigNamespaceName, new PageRequest(0, 10));
Page<Instance> anotherResult = instanceService.findInstancesByNamespaceAndInstanceAppId(anotherAppId,
someConfigAppId, someConfigClusterName, someConfigNamespaceName, new PageRequest(0, 10));
assertEquals(Lists.newArrayList(someInstance), result.getContent());
assertEquals(Lists.newArrayList(anotherInstance), anotherResult.getContent());
}
@Test @Test
@Rollback @Rollback
public void testFindInstanceConfigsByNamespaceWithReleaseKeysNotIn() throws Exception { public void testFindInstanceConfigsByNamespaceWithReleaseKeysNotIn() throws Exception {
......
package com.ctrip.framework.apollo.biz.service;
import com.ctrip.framework.apollo.biz.AbstractIntegrationTest;
import com.ctrip.framework.apollo.biz.entity.GrayReleaseRule;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.ReleaseHistory;
import com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus;
import com.ctrip.framework.apollo.common.constants.ReleaseOperation;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.util.AssertionErrors;
public class NamespaceBranchServiceTest extends AbstractIntegrationTest {
@Autowired
private NamespaceBranchService namespaceBranchService;
@Autowired
private ReleaseHistoryService releaseHistoryService;
private String testApp = "test";
private String testCluster = "default";
private String testNamespace = "application";
private String testBranchName = "child-cluster";
private String operator = "apollo";
private Pageable pageable = new PageRequest(0, 10);
@Test
@Sql(scripts = "/sql/namespace-branch-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testFindBranch() {
Namespace branch = namespaceBranchService.findBranch(testApp, testCluster, testNamespace);
Assert.assertNotNull(branch);
Assert.assertEquals(testBranchName, branch.getClusterName());
}
@Test
@Sql(scripts = "/sql/namespace-branch-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testUpdateBranchGrayRulesWithUpdateOnce() {
GrayReleaseRule rule = instanceGrayReleaseRule();
namespaceBranchService.updateBranchGrayRules(testApp, testCluster, testNamespace, testBranchName, rule);
GrayReleaseRule
activeRule =
namespaceBranchService.findBranchGrayRules(testApp, testCluster, testNamespace, testBranchName);
Assert.assertNotNull(activeRule);
Assert.assertEquals(rule.getAppId(), activeRule.getAppId());
Assert.assertEquals(rule.getRules(), activeRule.getRules());
Assert.assertEquals(Long.valueOf(0), activeRule.getReleaseId());
Page<ReleaseHistory> releaseHistories = releaseHistoryService.findReleaseHistoriesByNamespace
(testApp, testCluster, testNamespace, pageable);
ReleaseHistory releaseHistory = releaseHistories.getContent().get(0);
Assert.assertEquals(1, releaseHistories.getTotalElements());
Assert.assertEquals(ReleaseOperation.APPLY_GRAY_RULES, releaseHistory.getOperation());
Assert.assertEquals(0, releaseHistory.getReleaseId());
Assert.assertEquals(0, releaseHistory.getPreviousReleaseId());
Assert.assertTrue(releaseHistory.getOperationContext().contains(rule.getRules()));
}
@Test
@Sql(scripts = "/sql/namespace-branch-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testUpdateBranchGrayRulesWithUpdateTwice() {
GrayReleaseRule firstRule = instanceGrayReleaseRule();
namespaceBranchService.updateBranchGrayRules(testApp, testCluster, testNamespace, testBranchName, firstRule);
GrayReleaseRule secondRule = instanceGrayReleaseRule();
secondRule.setRules("[{\"clientAppId\":\"branch-test\",\"clientIpList\":[\"10.38.57.112\"]}]");
namespaceBranchService.updateBranchGrayRules(testApp, testCluster, testNamespace, testBranchName, secondRule);
GrayReleaseRule
activeRule =
namespaceBranchService.findBranchGrayRules(testApp, testCluster, testNamespace, testBranchName);
Assert.assertNotNull(secondRule);
Assert.assertEquals(secondRule.getAppId(), activeRule.getAppId());
Assert.assertEquals(secondRule.getRules(), activeRule.getRules());
Assert.assertEquals(Long.valueOf(0), activeRule.getReleaseId());
Page<ReleaseHistory> releaseHistories = releaseHistoryService.findReleaseHistoriesByNamespace
(testApp, testCluster, testNamespace, pageable);
ReleaseHistory firstReleaseHistory = releaseHistories.getContent().get(1);
ReleaseHistory secondReleaseHistory = releaseHistories.getContent().get(0);
Assert.assertEquals(2, releaseHistories.getTotalElements());
Assert.assertEquals(ReleaseOperation.APPLY_GRAY_RULES, firstReleaseHistory.getOperation());
Assert.assertEquals(ReleaseOperation.APPLY_GRAY_RULES, secondReleaseHistory.getOperation());
Assert.assertTrue(firstReleaseHistory.getOperationContext().contains(firstRule.getRules()));
Assert.assertFalse(firstReleaseHistory.getOperationContext().contains(secondRule.getRules()));
Assert.assertTrue(secondReleaseHistory.getOperationContext().contains(firstRule.getRules()));
Assert.assertTrue(secondReleaseHistory.getOperationContext().contains(secondRule.getRules()));
}
@Test
@Sql(scripts = "/sql/namespace-branch-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testUpdateRulesReleaseIdWithOldRuleNotExist() {
long latestReleaseId = 100;
namespaceBranchService
.updateRulesReleaseId(testApp, testCluster, testNamespace, testBranchName, latestReleaseId, operator);
GrayReleaseRule
activeRule =
namespaceBranchService.findBranchGrayRules(testApp, testCluster, testNamespace, testBranchName);
Assert.assertNull(activeRule);
}
@Test
@Sql(scripts = "/sql/namespace-branch-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testUpdateRulesReleaseIdWithOldRuleExist() {
GrayReleaseRule rule = instanceGrayReleaseRule();
namespaceBranchService.updateBranchGrayRules(testApp, testCluster, testNamespace, testBranchName, rule);
long latestReleaseId = 100;
namespaceBranchService
.updateRulesReleaseId(testApp, testCluster, testNamespace, testBranchName, latestReleaseId, operator);
GrayReleaseRule
activeRule =
namespaceBranchService.findBranchGrayRules(testApp, testCluster, testNamespace, testBranchName);
Assert.assertNotNull(activeRule);
Assert.assertEquals(Long.valueOf(latestReleaseId), activeRule.getReleaseId());
Assert.assertEquals(rule.getRules(), activeRule.getRules());
Assert.assertEquals(NamespaceBranchStatus.ACTIVE, activeRule.getBranchStatus());
}
@Test
@Sql(scripts = "/sql/namespace-branch-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testDeleteBranch() {
GrayReleaseRule rule = instanceGrayReleaseRule();
namespaceBranchService.updateBranchGrayRules(testApp, testCluster, testNamespace, testBranchName, rule);
namespaceBranchService.deleteBranch(testApp, testCluster, testNamespace, testBranchName, NamespaceBranchStatus.DELETED, operator);
Namespace branch = namespaceBranchService.findBranch(testApp, testCluster, testNamespace);
Assert.assertNull(branch);
GrayReleaseRule latestRule = namespaceBranchService.findBranchGrayRules(testApp, testCluster, testNamespace, testBranchName);
Assert.assertNotNull(latestRule);
Assert.assertEquals(NamespaceBranchStatus.DELETED, latestRule.getBranchStatus());
Assert.assertEquals("[]", latestRule.getRules());
Page<ReleaseHistory> releaseHistories = releaseHistoryService.findReleaseHistoriesByNamespace
(testApp, testCluster, testNamespace, pageable);
ReleaseHistory firstReleaseHistory = releaseHistories.getContent().get(1);
ReleaseHistory secondReleaseHistory = releaseHistories.getContent().get(0);
Assert.assertEquals(2, releaseHistories.getTotalElements());
Assert.assertEquals(ReleaseOperation.APPLY_GRAY_RULES, firstReleaseHistory.getOperation());
Assert.assertEquals(ReleaseOperation.ABANDON_GRAY_RELEASE, secondReleaseHistory.getOperation());
}
private GrayReleaseRule instanceGrayReleaseRule() {
GrayReleaseRule rule = new GrayReleaseRule();
rule.setAppId(testApp);
rule.setClusterName(testCluster);
rule.setNamespaceName(testNamespace);
rule.setBranchName(testBranchName);
rule.setBranchStatus(NamespaceBranchStatus.ACTIVE);
rule.setRules("[{\"clientAppId\":\"test\",\"clientIpList\":[\"1.0.0.4\"]}]");
return rule;
}
}
...@@ -40,7 +40,7 @@ public class PrivilegeServiceTest extends AbstractIntegrationTest { ...@@ -40,7 +40,7 @@ public class PrivilegeServiceTest extends AbstractIntegrationTest {
app.setDataChangeCreatedTime(new Date()); app.setDataChangeCreatedTime(new Date());
App newApp = adminService.createNewApp(app); App newApp = adminService.createNewApp(app);
List<Cluster> clusters = clusterService.findClusters(newApp.getAppId()); List<Cluster> clusters = clusterService.findParentClusters(newApp.getAppId());
List<Namespace> namespaces = List<Namespace> namespaces =
namespaceService.findNamespaces(newApp.getAppId(), clusters.get(0).getName()); namespaceService.findNamespaces(newApp.getAppId(), clusters.get(0).getName());
Namespace namespace = namespaces.get(0); Namespace namespace = namespaces.get(0);
...@@ -70,7 +70,7 @@ public class PrivilegeServiceTest extends AbstractIntegrationTest { ...@@ -70,7 +70,7 @@ public class PrivilegeServiceTest extends AbstractIntegrationTest {
app.setDataChangeLastModifiedBy(owner); app.setDataChangeLastModifiedBy(owner);
app.setDataChangeCreatedTime(new Date()); app.setDataChangeCreatedTime(new Date());
App newApp = adminService.createNewApp(app); App newApp = adminService.createNewApp(app);
List<Cluster> clusters = clusterService.findClusters(newApp.getAppId()); List<Cluster> clusters = clusterService.findParentClusters(newApp.getAppId());
List<Namespace> namespaces = List<Namespace> namespaces =
namespaceService.findNamespaces(newApp.getAppId(), clusters.get(0).getName()); namespaceService.findNamespaces(newApp.getAppId(), clusters.get(0).getName());
Namespace namespace = namespaces.get(0); Namespace namespace = namespaces.get(0);
......
package com.ctrip.framework.apollo.biz.service;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.biz.AbstractIntegrationTest;
import com.ctrip.framework.apollo.biz.entity.GrayReleaseRule;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.entity.ReleaseHistory;
import com.ctrip.framework.apollo.common.constants.ReleaseOperation;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.jdbc.Sql;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
public class ReleaseCreationTest extends AbstractIntegrationTest {
private Gson gson = new Gson();
private Type configurationTypeReference = new TypeToken<Map<String, String>>() {
}.getType();
@Autowired
private ReleaseService releaseService;
@Autowired
private NamespaceBranchService namespaceBranchService;
@Autowired
private ReleaseHistoryService releaseHistoryService;
private String testApp = "test";
private String testNamespace = "application";
private String operator = "apollo";
private Pageable pageable = new PageRequest(0, 10);
@Test
@Sql(scripts = "/sql/release-creation-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testPublishNormalNamespace() {
long namespaceId = 100;
String clusterName = "only-master";
Namespace namespace = instanceNamespace(namespaceId, clusterName);
releaseService.publish(namespace, "", "", operator);
Release latestRelease = releaseService.findLatestActiveRelease(namespace);
Assert.assertNotNull(latestRelease);
Map<String, String> configuration = parseConfiguration(latestRelease.getConfigurations());
Assert.assertEquals(3, configuration.size());
Assert.assertEquals("v1", configuration.get("k1"));
Assert.assertEquals("v2", configuration.get("k2"));
Assert.assertEquals("v3", configuration.get("k3"));
Page<ReleaseHistory> releaseHistories = releaseHistoryService.findReleaseHistoriesByNamespace
(testApp, clusterName, testNamespace, pageable);
ReleaseHistory releaseHistory = releaseHistories.getContent().get(0);
Assert.assertEquals(1, releaseHistories.getTotalElements());
Assert.assertEquals(ReleaseOperation.NORMAL_RELEASE, releaseHistory.getOperation());
Assert.assertEquals(latestRelease.getId(), releaseHistory.getReleaseId());
Assert.assertEquals(0, releaseHistory.getPreviousReleaseId());
}
/**
* Master | Branch
* ------------------------------ Master | Branch
* Items k1=v1 | ----------------------------
* k2=v2 | k1=v1 | k1=v1
* k3=v3 publish master k2=v2 | k2=v2
* ------------------------------ ===========>> Result k3=v3 | k3=v3
* Release |
* |
* |
*/
@Test
@Sql(scripts = "/sql/release-creation-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testPublishMasterNamespaceAndBranchHasNotItems() {
long parentNamespaceId = 101;
String parentClusterName = "default1";
long childNamespaceId = 102;
String childClusterName = "child-cluster1";
Namespace parentNamespace = instanceNamespace(parentNamespaceId, parentClusterName);
releaseService.publish(parentNamespace, "", "", operator);
Release latestParentNamespaceRelease = releaseService.findLatestActiveRelease(parentNamespace);
//assert parent namespace
Assert.assertNotNull(latestParentNamespaceRelease);
Map<String, String> parentNamespaceConfiguration = parseConfiguration(latestParentNamespaceRelease.getConfigurations());
Assert.assertEquals(3, parentNamespaceConfiguration.size());
Assert.assertEquals("v1", parentNamespaceConfiguration.get("k1"));
Assert.assertEquals("v2", parentNamespaceConfiguration.get("k2"));
Assert.assertEquals("v3", parentNamespaceConfiguration.get("k3"));
//assert child namespace
Namespace childNamespace = instanceNamespace(childNamespaceId, childClusterName);
Release latestChildNamespaceRelease = releaseService.findLatestActiveRelease(childNamespace);
//assert parent namespace
Assert.assertNotNull(latestChildNamespaceRelease);
Map<String, String> childNamespaceConfiguration = parseConfiguration(latestChildNamespaceRelease.getConfigurations());
Assert.assertEquals(3, childNamespaceConfiguration.size());
Assert.assertEquals("v1", childNamespaceConfiguration.get("k1"));
Assert.assertEquals("v2", childNamespaceConfiguration.get("k2"));
Assert.assertEquals("v3", childNamespaceConfiguration.get("k3"));
GrayReleaseRule rule= namespaceBranchService.findBranchGrayRules(testApp, parentClusterName,
testNamespace, childClusterName);
Assert.assertNotNull(rule);
Assert.assertEquals(1, rule.getBranchStatus());
Assert.assertEquals(Long.valueOf(latestChildNamespaceRelease.getId()), rule.getReleaseId());
//assert release history
Page<ReleaseHistory> releaseHistories = releaseHistoryService.findReleaseHistoriesByNamespace
(testApp, parentClusterName, testNamespace, pageable);
ReleaseHistory masterReleaseHistory = releaseHistories.getContent().get(1);
ReleaseHistory branchReleaseHistory = releaseHistories.getContent().get(0);
Assert.assertEquals(2, releaseHistories.getTotalElements());
Assert.assertEquals(ReleaseOperation.NORMAL_RELEASE, masterReleaseHistory.getOperation());
Assert.assertEquals(latestParentNamespaceRelease.getId(), masterReleaseHistory.getReleaseId());
Assert.assertEquals(0, masterReleaseHistory.getPreviousReleaseId());
Assert.assertEquals(ReleaseOperation.MASTER_NORMAL_RELEASE_MERGE_TO_GRAY,
branchReleaseHistory.getOperation());
Assert.assertEquals(latestChildNamespaceRelease.getId(), branchReleaseHistory.getReleaseId());
Assert.assertTrue(branchReleaseHistory.getOperationContext().contains(String.format
("\"baseReleaseId\":%d", latestParentNamespaceRelease.getId())));
Assert.assertTrue(branchReleaseHistory.getOperationContext().contains(rule.getRules()));
}
/**
* Master | Branch
* ------------------------------ Master | Branch
* Items k1=v1 | k1=v1-2 -------------------------
* k2=v2 | k1=v1 | k1=v1
* k3=v3 publish master k2=v2 | k2=v2
* ------------------------------ ===========>> Result k3=v3 | k3=v3
* Release |
* |
* |
*/
@Test
@Sql(scripts = "/sql/release-creation-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testPublishMasterNamespaceAndBranchHasItems() {
long parentNamespaceId = 103;
String parentClusterName = "default2";
long childNamespaceId = 104;
String childClusterName = "child-cluster2";
Namespace parentNamespace = instanceNamespace(parentNamespaceId, parentClusterName);
releaseService.publish(parentNamespace, "", "", operator);
Release latestParentNamespaceRelease = releaseService.findLatestActiveRelease(parentNamespace);
//assert parent namespace
Assert.assertNotNull(latestParentNamespaceRelease);
Map<String, String> parentNamespaceConfiguration = parseConfiguration(latestParentNamespaceRelease.getConfigurations());
Assert.assertEquals(3, parentNamespaceConfiguration.size());
Assert.assertEquals("v1", parentNamespaceConfiguration.get("k1"));
Assert.assertEquals("v2", parentNamespaceConfiguration.get("k2"));
Assert.assertEquals("v3", parentNamespaceConfiguration.get("k3"));
//assert child namespace
Namespace childNamespace = instanceNamespace(childNamespaceId, childClusterName);
Release latestChildNamespaceRelease = releaseService.findLatestActiveRelease(childNamespace);
Assert.assertNotNull(latestChildNamespaceRelease);
Map<String, String> childNamespaceConfiguration = parseConfiguration(latestChildNamespaceRelease.getConfigurations());
Assert.assertEquals(3, childNamespaceConfiguration.size());
Assert.assertEquals("v1", childNamespaceConfiguration.get("k1"));
Assert.assertEquals("v2", childNamespaceConfiguration.get("k2"));
Assert.assertEquals("v3", childNamespaceConfiguration.get("k3"));
GrayReleaseRule rule= namespaceBranchService.findBranchGrayRules(testApp, parentClusterName,
testNamespace, childClusterName);
Assert.assertNotNull(rule);
Assert.assertEquals(1, rule.getBranchStatus());
Assert.assertEquals(Long.valueOf(latestChildNamespaceRelease.getId()), rule.getReleaseId());
//assert release history
Page<ReleaseHistory> releaseHistories = releaseHistoryService.findReleaseHistoriesByNamespace
(testApp, parentClusterName, testNamespace, pageable);
ReleaseHistory masterReleaseHistory = releaseHistories.getContent().get(1);
ReleaseHistory branchReleaseHistory = releaseHistories.getContent().get(0);
Assert.assertEquals(2, releaseHistories.getTotalElements());
Assert.assertEquals(ReleaseOperation.NORMAL_RELEASE, masterReleaseHistory.getOperation());
Assert.assertEquals(latestParentNamespaceRelease.getId(), masterReleaseHistory.getReleaseId());
Assert.assertEquals(0, masterReleaseHistory.getPreviousReleaseId());
Assert.assertEquals(ReleaseOperation.MASTER_NORMAL_RELEASE_MERGE_TO_GRAY,
branchReleaseHistory.getOperation());
Assert.assertEquals(latestChildNamespaceRelease.getId(), branchReleaseHistory.getReleaseId());
Assert.assertTrue(branchReleaseHistory.getOperationContext().contains(String.format
("\"baseReleaseId\":%d", latestParentNamespaceRelease.getId())));
Assert.assertTrue(branchReleaseHistory.getOperationContext().contains(rule.getRules()));
}
/**
* Master | Branch
* ------------------------------ Master | Branch
* Items k1=v1 | k1=v1-2 ----------------------------
* k2=v2-2 | publish master k1=v1 | k1=v1-1
* ------------------------------ ===========>> Result k2=v2-2 | k2=v2-2
* Release k1=v1 | k1=v1-1 |
* k2=v2 | k2=v3
* k3=v3 | k3=v3
*/
@Test
@Sql(scripts = "/sql/release-creation-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testModifyMasterNamespaceItemsAndBranchAlsoModify() {
long parentNamespaceId = 105;
String parentClusterName = "default3";
long childNamespaceId = 106;
String childClusterName = "child-cluster3";
Namespace parentNamespace = instanceNamespace(parentNamespaceId, parentClusterName);
releaseService.publish(parentNamespace, "", "", operator);
Release latestParentNamespaceRelease = releaseService.findLatestActiveRelease(parentNamespace);
//assert parent namespace
Assert.assertNotNull(latestParentNamespaceRelease);
Map<String, String> parentNamespaceConfiguration = parseConfiguration(latestParentNamespaceRelease.getConfigurations());
Assert.assertEquals(2, parentNamespaceConfiguration.size());
Assert.assertEquals("v1", parentNamespaceConfiguration.get("k1"));
Assert.assertEquals("v2-2", parentNamespaceConfiguration.get("k2"));
//assert child namespace
Namespace childNamespace = instanceNamespace(childNamespaceId, childClusterName);
Release latestChildNamespaceRelease = releaseService.findLatestActiveRelease(childNamespace);
Assert.assertNotNull(latestChildNamespaceRelease);
Map<String, String> childNamespaceConfiguration = parseConfiguration(latestChildNamespaceRelease.getConfigurations());
Assert.assertEquals(2, childNamespaceConfiguration.size());
Assert.assertEquals("v1-1", childNamespaceConfiguration.get("k1"));
Assert.assertEquals("v2-2", childNamespaceConfiguration.get("k2"));
GrayReleaseRule rule= namespaceBranchService.findBranchGrayRules(testApp, parentClusterName,
testNamespace, childClusterName);
Assert.assertNotNull(rule);
Assert.assertEquals(1, rule.getBranchStatus());
Assert.assertEquals(Long.valueOf(latestChildNamespaceRelease.getId()), rule.getReleaseId());
//assert release history
Page<ReleaseHistory> releaseHistories = releaseHistoryService.findReleaseHistoriesByNamespace
(testApp, parentClusterName, testNamespace, pageable);
ReleaseHistory masterReleaseHistory = releaseHistories.getContent().get(1);
ReleaseHistory branchReleaseHistory = releaseHistories.getContent().get(0);
Assert.assertEquals(2, releaseHistories.getTotalElements());
Assert.assertEquals(ReleaseOperation.NORMAL_RELEASE, masterReleaseHistory.getOperation());
Assert.assertEquals(latestParentNamespaceRelease.getId(), masterReleaseHistory.getReleaseId());
Assert.assertEquals(1, masterReleaseHistory.getPreviousReleaseId());
Assert.assertEquals(ReleaseOperation.MASTER_NORMAL_RELEASE_MERGE_TO_GRAY,
branchReleaseHistory.getOperation());
Assert.assertEquals(latestChildNamespaceRelease.getId(), branchReleaseHistory.getReleaseId());
Assert.assertEquals(2, branchReleaseHistory.getPreviousReleaseId());
Assert.assertTrue(branchReleaseHistory.getOperationContext().contains(String.format
("\"baseReleaseId\":%d", latestParentNamespaceRelease.getId())));
Assert.assertTrue(branchReleaseHistory.getOperationContext().contains(rule.getRules()));
}
/**
* Master | Branch
* ------------------------------ Master | Branch
* Items k1=v1 | k1=v1-2 ----------------------------
* k2=v2-2 | k4=v4 publish branch k1=v1 | k1=v1-2
* ------------------------------ ===========>> Result k2=v2 | k2=v2
* Release k1=v1 | k3=v3 | k3=v3
* k2=v2 | | k4=v4
* k3=v3 |
*/
@Test
@Sql(scripts = "/sql/release-creation-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testPublishBranchAtFirstTime() {
long parentNamespaceId = 107;
String parentClusterName = "default4";
long childNamespaceId = 108;
String childClusterName = "child-cluster4";
//assert child namespace
Namespace childNamespace = instanceNamespace(childNamespaceId, childClusterName);
releaseService.publish(childNamespace, "", "", operator);
Release latestChildNamespaceRelease = releaseService.findLatestActiveRelease(childNamespace);
Assert.assertNotNull(latestChildNamespaceRelease);
Map<String, String> childNamespaceConfiguration = parseConfiguration(latestChildNamespaceRelease.getConfigurations());
Assert.assertEquals(4, childNamespaceConfiguration.size());
Assert.assertEquals("v1-2", childNamespaceConfiguration.get("k1"));
Assert.assertEquals("v2", childNamespaceConfiguration.get("k2"));
Assert.assertEquals("v3", childNamespaceConfiguration.get("k3"));
Assert.assertEquals("v4", childNamespaceConfiguration.get("k4"));
Namespace parentNamespace = instanceNamespace(parentNamespaceId, parentClusterName);
Release latestParentNamespaceRelease = releaseService.findLatestActiveRelease(parentNamespace);
//assert parent namespace
Assert.assertNotNull(latestParentNamespaceRelease);
Map<String, String> parentNamespaceConfiguration = parseConfiguration(latestParentNamespaceRelease.getConfigurations());
Assert.assertEquals(3, parentNamespaceConfiguration.size());
Assert.assertEquals("v1", parentNamespaceConfiguration.get("k1"));
Assert.assertEquals("v2", parentNamespaceConfiguration.get("k2"));
Assert.assertEquals("v3", parentNamespaceConfiguration.get("k3"));
GrayReleaseRule rule= namespaceBranchService.findBranchGrayRules(testApp, parentClusterName,
testNamespace, childClusterName);
Assert.assertNotNull(rule);
Assert.assertEquals(1, rule.getBranchStatus());
Assert.assertEquals(Long.valueOf(latestChildNamespaceRelease.getId()), rule.getReleaseId());
//assert release history
Page<ReleaseHistory> releaseHistories = releaseHistoryService.findReleaseHistoriesByNamespace
(testApp, parentClusterName, testNamespace, pageable);
ReleaseHistory branchReleaseHistory = releaseHistories.getContent().get(0);
Assert.assertEquals(1, releaseHistories.getTotalElements());
Assert.assertEquals(ReleaseOperation.GRAY_RELEASE,
branchReleaseHistory.getOperation());
Assert.assertEquals(latestChildNamespaceRelease.getId(), branchReleaseHistory.getReleaseId());
Assert.assertEquals(0, branchReleaseHistory.getPreviousReleaseId());
Assert.assertTrue(branchReleaseHistory.getOperationContext().contains("\"baseReleaseId\":3"));
Assert.assertTrue(branchReleaseHistory.getOperationContext().contains(rule.getRules()));
}
/**
* Master | Branch
* ------------------------------ Master | Branch
* Items k1=v1 | k1=v1-2 ----------------------------
* k2=v2-2 | k4=v4 k1=v1 | k1=v1-2
* k6=v6 publish branch k2=v2 | k2=v2
* ------------------------------ ===========>> Result k3=v3 | k3=v3
* Release k1=v1 | k1=v1-1 | k4=v4
* k2=v2 | k2=v2 | k6=v6
* k3=v3 | k3=v3
* | k4=v4
* | k5=v5
*/
@Test
@Sql(scripts = "/sql/release-creation-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testPublishBranch() {
long parentNamespaceId = 109;
String parentClusterName = "default5";
long childNamespaceId = 1010;
String childClusterName = "child-cluster5";
//assert child namespace
Namespace childNamespace = instanceNamespace(childNamespaceId, childClusterName);
releaseService.publish(childNamespace, "", "", operator);
Release latestChildNamespaceRelease = releaseService.findLatestActiveRelease(childNamespace);
Assert.assertNotNull(latestChildNamespaceRelease);
Map<String, String> childNamespaceConfiguration = parseConfiguration(latestChildNamespaceRelease.getConfigurations());
Assert.assertEquals(5, childNamespaceConfiguration.size());
Assert.assertEquals("v1-2", childNamespaceConfiguration.get("k1"));
Assert.assertEquals("v2", childNamespaceConfiguration.get("k2"));
Assert.assertEquals("v3", childNamespaceConfiguration.get("k3"));
Assert.assertEquals("v4", childNamespaceConfiguration.get("k4"));
Assert.assertEquals("v6", childNamespaceConfiguration.get("k6"));
Namespace parentNamespace = instanceNamespace(parentNamespaceId, parentClusterName);
Release latestParentNamespaceRelease = releaseService.findLatestActiveRelease(parentNamespace);
//assert parent namespace
Assert.assertNotNull(latestParentNamespaceRelease);
Map<String, String> parentNamespaceConfiguration = parseConfiguration(latestParentNamespaceRelease.getConfigurations());
Assert.assertEquals(3, parentNamespaceConfiguration.size());
Assert.assertEquals("v1", parentNamespaceConfiguration.get("k1"));
Assert.assertEquals("v2", parentNamespaceConfiguration.get("k2"));
Assert.assertEquals("v3", parentNamespaceConfiguration.get("k3"));
GrayReleaseRule rule= namespaceBranchService.findBranchGrayRules(testApp, parentClusterName,
testNamespace, childClusterName);
Assert.assertNotNull(rule);
Assert.assertEquals(1, rule.getBranchStatus());
Assert.assertEquals(Long.valueOf(latestChildNamespaceRelease.getId()), rule.getReleaseId());
//assert release history
Page<ReleaseHistory> releaseHistories = releaseHistoryService.findReleaseHistoriesByNamespace
(testApp, parentClusterName, testNamespace, pageable);
ReleaseHistory branchReleaseHistory = releaseHistories.getContent().get(0);
Assert.assertEquals(1, releaseHistories.getTotalElements());
Assert.assertEquals(ReleaseOperation.GRAY_RELEASE,
branchReleaseHistory.getOperation());
Assert.assertEquals(latestChildNamespaceRelease.getId(), branchReleaseHistory.getReleaseId());
Assert.assertEquals(5, branchReleaseHistory.getPreviousReleaseId());
Assert.assertTrue(branchReleaseHistory.getOperationContext().contains("\"baseReleaseId\":4"));
Assert.assertTrue(branchReleaseHistory.getOperationContext().contains(rule.getRules()));
}
/**
* Master | Branch
* ------------------------------ Master | Branch
* Rollback Release k1=v1 | k1=v1-2 ----------------------------
* k2=v2 | k2=v2 k1=v1-1 | k1=v1-2
* | k3=v3 k2=v2-1 | k2=v2-1
* rollback k3=v3 | k3=v3
* ------------------------------ ===========>> New Release
* New Release k1=v1-1 |
* k2=v2-1 |
* k3=v3 |
*
*
*/
@Test
@Sql(scripts = "/sql/release-creation-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testRollback() {
String parentClusterName = "default6";
String childClusterName = "child-cluster6";
String operator = "apollo";
Release parentNamespaceLatestRelease = releaseService.findLatestActiveRelease(testApp, parentClusterName, testNamespace);
releaseService.rollback(parentNamespaceLatestRelease.getId(), operator);
Release parentNamespaceNewLatestRelease = releaseService.findLatestActiveRelease(testApp, parentClusterName, testNamespace);
Map<String, String> parentNamespaceConfiguration = parseConfiguration(parentNamespaceNewLatestRelease.getConfigurations());
Assert.assertEquals(3, parentNamespaceConfiguration.size());
Assert.assertEquals("v1-1", parentNamespaceConfiguration.get("k1"));
Assert.assertEquals("v2-1", parentNamespaceConfiguration.get("k2"));
Assert.assertEquals("v3", parentNamespaceConfiguration.get("k3"));
Release childNamespaceNewLatestRelease = releaseService.findLatestActiveRelease(testApp, childClusterName, testNamespace);
Map<String, String> childNamespaceConfiguration = parseConfiguration(childNamespaceNewLatestRelease.getConfigurations());
Assert.assertEquals(3, childNamespaceConfiguration.size());
Assert.assertEquals("v1-2", childNamespaceConfiguration.get("k1"));
Assert.assertEquals("v2-1", childNamespaceConfiguration.get("k2"));
Assert.assertEquals("v3", childNamespaceConfiguration.get("k3"));
//assert release history
Page<ReleaseHistory> releaseHistories = releaseHistoryService.findReleaseHistoriesByNamespace
(testApp, parentClusterName, testNamespace, pageable);
ReleaseHistory masterReleaseHistory = releaseHistories.getContent().get(1);
ReleaseHistory branchReleaseHistory = releaseHistories.getContent().get(0);
Assert.assertEquals(2, releaseHistories.getTotalElements());
Assert.assertEquals(ReleaseOperation.ROLLBACK, masterReleaseHistory.getOperation());
Assert.assertEquals(6, masterReleaseHistory.getReleaseId());
Assert.assertEquals(7, masterReleaseHistory.getPreviousReleaseId());
Assert.assertEquals(ReleaseOperation.MATER_ROLLBACK_MERGE_TO_GRAY,
branchReleaseHistory.getOperation());
Assert.assertEquals(childNamespaceNewLatestRelease.getId(), branchReleaseHistory.getReleaseId());
Assert.assertEquals(8, branchReleaseHistory.getPreviousReleaseId());
Assert.assertTrue(branchReleaseHistory.getOperationContext().contains(String.format
("\"baseReleaseId\":%d", parentNamespaceNewLatestRelease.getId())));
}
private Namespace instanceNamespace(long id, String clusterName) {
Namespace namespace = new Namespace();
namespace.setAppId(testApp);
namespace.setNamespaceName(testNamespace);
namespace.setId(id);
namespace.setClusterName(clusterName);
return namespace;
}
private Map<String, String> parseConfiguration(String configuration) {
return gson.fromJson(configuration, configurationTypeReference);
}
}
...@@ -32,6 +32,10 @@ public class ReleaseServiceTest extends AbstractUnitTest { ...@@ -32,6 +32,10 @@ public class ReleaseServiceTest extends AbstractUnitTest {
private ReleaseRepository releaseRepository; private ReleaseRepository releaseRepository;
@Mock @Mock
private NamespaceService namespaceService; private NamespaceService namespaceService;
@Mock
private ReleaseHistoryService releaseHistoryService;
@Mock
private ItemSetService itemSetService;
@InjectMocks @InjectMocks
private ReleaseService releaseService; private ReleaseService releaseService;
......
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003171', 'application', false); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003171', 'application', false);
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003171', 'fx.apollo.config', true); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003171', 'fx.apollo.config', true);
INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003172', 'application', false); INSERT INTO AppNamespace (AppId, Name, IsPublic) VALUES ('100003172', 'application', false);
......
DELETE FROM App;
DELETE FROM Cluster;
DELETE FROM namespace;
DELETE FROM grayreleaserule;
DELETE FROM release;
DELETE FROM item;
DELETE FROM releasemessage;
DELETE FROM releasehistory;
INSERT INTO `app` ( `AppId`, `Name`, `OrgId`, `OrgName`, `OwnerName`, `OwnerEmail`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES('test', 'test0620-06', 'default', 'default', 'default', 'default', 0, 'default', 'default');
INSERT INTO `cluster` (`ID`, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES (1, 'default', 'test', 0, 0, 'default', 'default');
INSERT INTO `cluster` (`Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES('child-cluster', 'test', 1, 0, 'default', 'default');
INSERT INTO `namespace` (`AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES('test', 'default', 'application', 0, 'apollo', 'apollo');
INSERT INTO `namespace` (`AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES('test', 'child-cluster', 'application', 0, 'apollo', 'apollo');
INSERT INTO `app` ( `AppId`, `Name`, `OrgId`, `OrgName`, `OwnerName`, `OwnerEmail`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES('test', 'test0620-06', 'default', 'default', 'default', 'default', 0, 'default', 'default');
/* normal namespace*/
INSERT INTO `cluster` ( `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES ( 'only-master', 'test', 0, 0, 'default', 'default');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(100, 'test', 'only-master', 'application', 0, 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(100, 'k1', 'v1', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(100, 'k2', 'v2', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(100, 'k3', 'v3', '', 'apollo', 'apollo');
/* namespace has branch. master has items but branch has not item*/
INSERT INTO `cluster` (ID, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES (101, 'default1', 'test', 0, 0, 'default', 'default');
INSERT INTO `cluster` (ID, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(102, 'child-cluster1', 'test', 101, 0, 'default', 'default');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(101, 'test', 'default1', 'application', 0, 'apollo', 'apollo');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(102, 'test', 'child-cluster1', 'application', 0, 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(101, 'k1', 'v1', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(101, 'k2', 'v2', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(101, 'k3', 'v3', '', 'apollo', 'apollo');
INSERT INTO `grayreleaserule` (`AppId`, `ClusterName`, `NamespaceName`, `BranchName`, `Rules`, `ReleaseId`, `BranchStatus`)VALUES ('test', 'default1', 'application', 'child-cluster1', '[]', 1155, 1);
/* namespace has branch. master has items and branch has item*/
INSERT INTO `cluster` (ID, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES (103, 'default2', 'test', 0, 0, 'default', 'default');
INSERT INTO `cluster` (ID, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(104, 'child-cluster2', 'test', 103, 0, 'default', 'default');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(103, 'test', 'default2', 'application', 0, 'apollo', 'apollo');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(104, 'test', 'child-cluster2', 'application', 0, 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(103, 'k1', 'v1', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(103, 'k2', 'v2', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(103, 'k3', 'v3', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(104, 'k1', 'v1-1', '', 'apollo', 'apollo');
INSERT INTO `grayreleaserule` (`AppId`, `ClusterName`, `NamespaceName`, `BranchName`, `Rules`, `ReleaseId`, `BranchStatus`)VALUES ('test', 'default2', 'application', 'child-cluster2', '[]', 1155, 1);
/* namespace has branch. master has items and branch has cover item */
INSERT INTO `cluster` (ID, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES (105, 'default3', 'test', 0, 0, 'default', 'default');
INSERT INTO `cluster` (ID, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(106, 'child-cluster3', 'test', 105, 0, 'default', 'default');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(105, 'test', 'default3', 'application', 0, 'apollo', 'apollo');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(106, 'test', 'child-cluster3', 'application', 0, 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(105, 'k1', 'v1', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(105, 'k2', 'v2-2', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(106, 'k1', 'v1-2', '', 'apollo', 'apollo');
INSERT INTO `release` (`Id`, `ReleaseKey`, `Name`, `Comment`, `AppId`, `ClusterName`, `NamespaceName`, `Configurations`, `IsAbandoned`)VALUES(1, '20160823102253-fc0071ddf9fd3260', '20160823101703-release', '', 'test', 'default3', 'application', '{"k1":"v1","k2":"v2","k3":"v3"}', 0);
INSERT INTO `release` (`Id`, `ReleaseKey`, `Name`, `Comment`, `AppId`, `ClusterName`, `NamespaceName`, `Configurations`, `IsAbandoned`)VALUES(2, '20160823102253-fc0071ddf9fd3260', '20160823101703-release', '', 'test', 'child-cluster3', 'application', '{"k1":"v1-1","k2":"v2","k3":"v3"}', 0);
INSERT INTO `grayreleaserule` (`AppId`, `ClusterName`, `NamespaceName`, `BranchName`, `Rules`, `ReleaseId`, `BranchStatus`)VALUES ('test', 'default3', 'application', 'child-cluster3', '[]', 1155, 1);
/*publish branch at first time */
INSERT INTO `cluster` (ID, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES (107, 'default4', 'test', 0, 0, 'default', 'default');
INSERT INTO `cluster` (ID, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(108, 'child-cluster4', 'test', 107, 0, 'default', 'default');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(107, 'test', 'default4', 'application', 0, 'apollo', 'apollo');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(108, 'test', 'child-cluster4', 'application', 0, 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(107, 'k1', 'v1', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(107, 'k2', 'v2-2', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(108, 'k1', 'v1-2', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(108, 'k4', 'v4', '', 'apollo', 'apollo');
INSERT INTO `release` (`Id`, `ReleaseKey`, `Name`, `Comment`, `AppId`, `ClusterName`, `NamespaceName`, `Configurations`, `IsAbandoned`)VALUES(3, '20160823102253-fc0071ddf9fd3260', '20160823101703-release', '', 'test', 'default4', 'application', '{"k1":"v1","k2":"v2","k3":"v3"}', 0);
INSERT INTO `grayreleaserule` (`AppId`, `ClusterName`, `NamespaceName`, `BranchName`, `Rules`, `ReleaseId`, `BranchStatus`)VALUES ('test', 'default4', 'application', 'child-cluster4', '[]', 1155, 1);
/*publish branch*/
INSERT INTO `cluster` (ID, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES (109, 'default5', 'test', 0, 0, 'default', 'default');
INSERT INTO `cluster` (ID, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(1010, 'child-cluster5', 'test', 109, 0, 'default', 'default');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(109, 'test', 'default5', 'application', 0, 'apollo', 'apollo');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(1010, 'test', 'child-cluster5', 'application', 0, 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(109, 'k1', 'v1', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(109, 'k2', 'v2-2', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(1010, 'k1', 'v1-2', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(1010, 'k4', 'v4', '', 'apollo', 'apollo');
INSERT INTO `item` (`NamespaceId`, `Key`, `Value`, `Comment`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(1010, 'k6', 'v6', '', 'apollo', 'apollo');
INSERT INTO `release` (`Id`, `ReleaseKey`, `Name`, `Comment`, `AppId`, `ClusterName`, `NamespaceName`, `Configurations`, `IsAbandoned`)VALUES(4, '20160823102253-fc0071ddf9fd3260', '20160823101703-release', '', 'test', 'default5', 'application', '{"k1":"v1","k2":"v2","k3":"v3"}', 0);
INSERT INTO `release` (`Id`, `ReleaseKey`, `Name`, `Comment`, `AppId`, `ClusterName`, `NamespaceName`, `Configurations`, `IsAbandoned`)VALUES(5, '20160823102253-fc0071ddf9fd3260', '20160823101703-release', '', 'test', 'child-cluster5', 'application', '{"k1":"v1-1","k2":"v2","k3":"v3","k4":"v4","k5":"v5"}', 0);
INSERT INTO `grayreleaserule` (`AppId`, `ClusterName`, `NamespaceName`, `BranchName`, `Rules`, `ReleaseId`, `BranchStatus`)VALUES ('test', 'default5', 'application', 'child-cluster5', '[]', 1155, 1);
/* rollback */
INSERT INTO `cluster` (ID, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES (1011, 'default6', 'test', 0, 0, 'default', 'default');
INSERT INTO `cluster` (ID, `Name`, `AppId`, `ParentClusterId`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(1012, 'child-cluster6', 'test', 1011, 0, 'default', 'default');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(1011, 'test', 'default6', 'application', 0, 'apollo', 'apollo');
INSERT INTO `namespace` (ID, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(1012, 'test', 'child-cluster6', 'application', 0, 'apollo', 'apollo');
INSERT INTO `release` (`Id`, `ReleaseKey`, `Name`, `Comment`, `AppId`, `ClusterName`, `NamespaceName`, `Configurations`, `IsAbandoned`)VALUES(6, '20160823102253-fc0071ddf9fd3260', '20160823101703-release', '', 'test', 'default6', 'application', '{"k1":"v1-1","k2":"v2-1","k3":"v3"}', 0);
INSERT INTO `release` (`Id`, `ReleaseKey`, `Name`, `Comment`, `AppId`, `ClusterName`, `NamespaceName`, `Configurations`, `IsAbandoned`)VALUES(7, '20160823102253-fc0071ddf9fd3260', '20160823101703-release', '', 'test', 'default6', 'application', '{"k1":"v1","k2":"v2"}', 0);
INSERT INTO `release` (`Id`, `ReleaseKey`, `Name`, `Comment`, `AppId`, `ClusterName`, `NamespaceName`, `Configurations`, `IsAbandoned`)VALUES(8, '20160823102253-fc0071ddf9fd3260', '20160823101703-release', '', 'test', 'child-cluster6', 'application', '{"k1":"v1-2","k2":"v2-1","k3":"v3"}', 0);
package com.ctrip.framework.apollo.ds; package com.ctrip.framework.apollo.ds;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.foundation.Foundation;
import org.codehaus.plexus.logging.LogEnabled; import org.codehaus.plexus.logging.LogEnabled;
import org.codehaus.plexus.logging.Logger; import org.codehaus.plexus.logging.Logger;
import org.unidal.dal.jdbc.datasource.DataSourceProvider; import org.unidal.dal.jdbc.datasource.DataSourceProvider;
...@@ -7,11 +12,6 @@ import org.unidal.dal.jdbc.datasource.model.entity.DataSourcesDef; ...@@ -7,11 +12,6 @@ import org.unidal.dal.jdbc.datasource.model.entity.DataSourcesDef;
import org.unidal.dal.jdbc.datasource.model.transform.DefaultSaxParser; import org.unidal.dal.jdbc.datasource.model.transform.DefaultSaxParser;
import org.unidal.lookup.annotation.Named; import org.unidal.lookup.annotation.Named;
import com.ctrip.framework.apollo.ConfigFile;
import com.ctrip.framework.apollo.ConfigService;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.foundation.Foundation;
/** /**
* Data source provider based on Apollo configuration service. * Data source provider based on Apollo configuration service.
* <p> * <p>
......
package com.ctrip.framework.apollo.model; package com.ctrip.framework.apollo.model;
import com.google.common.base.Objects;
import com.ctrip.framework.apollo.enums.PropertyChangeType; import com.ctrip.framework.apollo.enums.PropertyChangeType;
/** /**
......
...@@ -47,7 +47,7 @@ public class ConfigUtil { ...@@ -47,7 +47,7 @@ public class ConfigUtil {
String appId = Foundation.app().getAppId(); String appId = Foundation.app().getAppId();
if (Strings.isNullOrEmpty(appId)) { if (Strings.isNullOrEmpty(appId)) {
appId = ConfigConsts.NO_APPID_PLACEHOLDER; appId = ConfigConsts.NO_APPID_PLACEHOLDER;
logger.warn("app.id is not set, apollo will only load public namespace configurations!"); logger.warn("app.id is not set, please make sure it is set in classpath:/META-INF/app.properties, now apollo will only load public namespace configurations!");
} }
return appId; return appId;
} }
......
package com.ctrip.framework.apollo.common.constants;
import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleItemDTO;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Set;
public interface GsonType {
Type CONFIG = new TypeToken<Map<String, String>>() {}.getType();
}
package com.ctrip.framework.apollo.common.constants;
public interface NamespaceBranchStatus {
int DELETED = 0;
int ACTIVE = 1;
int MERGED = 2;
}
package com.ctrip.framework.apollo.common.constants;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public interface ReleaseOperation {
int NORMAL_RELEASE = 0;
int ROLLBACK = 1;
int GRAY_RELEASE = 2;
int APPLY_GRAY_RULES = 3;
int GRAY_RELEASE_MERGE_TO_MASTER = 4;
int MASTER_NORMAL_RELEASE_MERGE_TO_GRAY = 5;
int MATER_ROLLBACK_MERGE_TO_GRAY = 6;
int ABANDON_GRAY_RELEASE = 7;
int GRAY_RELEASE_DELETED_AFTER_MERGE = 8;
}
package com.ctrip.framework.apollo.common.constants;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public interface ReleaseOperationContext {
String SOURCE_BRANCH = "sourceBranch";
String RULES = "rules";
String OLD_RULES = "oldRules";
String BASE_RELEASE_ID = "baseReleaseId";
}
...@@ -3,12 +3,10 @@ package com.ctrip.framework.apollo.common.controller; ...@@ -3,12 +3,10 @@ package com.ctrip.framework.apollo.common.controller;
import javax.servlet.DispatcherType; import javax.servlet.DispatcherType;
import org.springframework.boot.context.embedded.FilterRegistrationBean; import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.boot.context.embedded.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.dianping.cat.servlet.CatFilter; import com.dianping.cat.servlet.CatFilter;
import com.dianping.cat.servlet.CatListener;
@Configuration @Configuration
public class CatConfig { public class CatConfig {
...@@ -23,11 +21,4 @@ public class CatConfig { ...@@ -23,11 +21,4 @@ public class CatConfig {
return bean; return bean;
} }
@Bean
public ServletListenerRegistrationBean<CatListener> catListener() {
ServletListenerRegistrationBean<CatListener> bean =
new ServletListenerRegistrationBean<CatListener>(new CatListener());
bean.setName("cat-listener");
return bean;
}
} }
...@@ -8,6 +8,8 @@ public class ClusterDTO extends BaseDTO{ ...@@ -8,6 +8,8 @@ public class ClusterDTO extends BaseDTO{
private String appId; private String appId;
private long parentClusterId;
public long getId() { public long getId() {
return id; return id;
} }
...@@ -31,4 +33,12 @@ public class ClusterDTO extends BaseDTO{ ...@@ -31,4 +33,12 @@ public class ClusterDTO extends BaseDTO{
public void setAppId(String appId) { public void setAppId(String appId) {
this.appId = appId; this.appId = appId;
} }
public long getParentClusterId() {
return parentClusterId;
}
public void setParentClusterId(long parentClusterId) {
this.parentClusterId = parentClusterId;
}
} }
package com.ctrip.framework.apollo.common.dto;
import com.google.common.collect.Sets;
import java.util.Set;
public class GrayReleaseRuleDTO extends BaseDTO {
private String appId;
private String clusterName;
private String namespaceName;
private String branchName;
private Set<GrayReleaseRuleItemDTO> ruleItems;
private Long releaseId;
public GrayReleaseRuleDTO(String appId, String clusterName, String namespaceName, String branchName) {
this.appId = appId;
this.clusterName = clusterName;
this.namespaceName = namespaceName;
this.branchName = branchName;
this.ruleItems = Sets.newHashSet();
}
public String getAppId() {
return appId;
}
public String getClusterName() {
return clusterName;
}
public String getNamespaceName() {
return namespaceName;
}
public String getBranchName() {
return branchName;
}
public Set<GrayReleaseRuleItemDTO> getRuleItems() {
return ruleItems;
}
public void setRuleItems(Set<GrayReleaseRuleItemDTO> ruleItems) {
this.ruleItems = ruleItems;
}
public void addRuleItem(GrayReleaseRuleItemDTO ruleItem) {
this.ruleItems.add(ruleItem);
}
public Long getReleaseId() {
return releaseId;
}
public void setReleaseId(Long releaseId) {
this.releaseId = releaseId;
}
}
package com.ctrip.framework.apollo.common.dto;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.Set;
import static com.google.common.base.MoreObjects.toStringHelper;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class GrayReleaseRuleItemDTO {
public static final String ALL_IP = "*";
private String clientAppId;
private Set<String> clientIpList;
public GrayReleaseRuleItemDTO(String clientAppId) {
this(clientAppId, Sets.newHashSet());
}
public GrayReleaseRuleItemDTO(String clientAppId, Set<String> clientIpList) {
this.clientAppId = clientAppId;
this.clientIpList = clientIpList;
}
public String getClientAppId() {
return clientAppId;
}
public Set<String> getClientIpList() {
return clientIpList;
}
public boolean matches(String clientAppId, String clientIp) {
return appIdMatches(clientAppId) && ipMatches(clientIp);
}
private boolean appIdMatches(String clientAppId) {
return this.clientAppId.equals(clientAppId);
}
private boolean ipMatches(String clientIp) {
return this.clientIpList.contains(ALL_IP) || clientIpList.contains(clientIp);
}
@Override
public String toString() {
return toStringHelper(this).add("clientAppId", clientAppId)
.add("clientIpList", clientIpList).toString();
}
}
...@@ -37,4 +37,8 @@ public class PageDTO<T> { ...@@ -37,4 +37,8 @@ public class PageDTO<T> {
public int getSize() { public int getSize() {
return size; return size;
} }
public boolean hasContent(){
return content != null && content.size() > 0;
}
} }
package com.ctrip.framework.apollo.common.dto;
import java.util.Map;
public class ReleaseHistoryDTO extends BaseDTO{
private long id;
private String appId;
private String clusterName;
private String namespaceName;
private String branchName;
private long releaseId;
private long previousReleaseId;
private int operation;
private Map<String, Object> operationContext;
public ReleaseHistoryDTO(){}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public String getNamespaceName() {
return namespaceName;
}
public void setNamespaceName(String namespaceName) {
this.namespaceName = namespaceName;
}
public String getBranchName() {
return branchName;
}
public void setBranchName(String branchName) {
this.branchName = branchName;
}
public long getReleaseId() {
return releaseId;
}
public void setReleaseId(long releaseId) {
this.releaseId = releaseId;
}
public long getPreviousReleaseId() {
return previousReleaseId;
}
public void setPreviousReleaseId(long previousReleaseId) {
this.previousReleaseId = previousReleaseId;
}
public int getOperation() {
return operation;
}
public void setOperation(int operation) {
this.operation = operation;
}
public Map<String, Object> getOperationContext() {
return operationContext;
}
public void setOperationContext(Map<String, Object> operationContext) {
this.operationContext = operationContext;
}
}
package com.ctrip.framework.apollo.common.utils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleItemDTO;
import java.lang.reflect.Type;
import java.util.Set;
/**
* @author Jason Song(song_s@ctrip.com)
*/
public class GrayReleaseRuleItemTransformer {
private static final Gson gson = new Gson();
private static final Type grayReleaseRuleItemsType = new TypeToken<Set<GrayReleaseRuleItemDTO>>() {
}.getType();
public static Set<GrayReleaseRuleItemDTO> batchTransformFromJSON(String content) {
return gson.fromJson(content, grayReleaseRuleItemsType);
}
public static String batchTransformToJSON(Set<GrayReleaseRuleItemDTO> ruleItems) {
return gson.toJson(ruleItems);
}
}
package com.ctrip.framework.apollo.common.utils;
import com.google.common.base.Joiner;
import com.ctrip.framework.apollo.common.dto.ClusterDTO;
import com.ctrip.framework.apollo.core.utils.ByteUtil;
import com.ctrip.framework.apollo.core.utils.MachineUtil;
import org.apache.commons.lang.time.FastDateFormat;
import java.security.SecureRandom;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
public class UniqueKeyGenerator {
private static final FastDateFormat TIMESTAMP_FORMAT = FastDateFormat.getInstance("yyyyMMddHHmmss");
private static final AtomicInteger counter = new AtomicInteger(new SecureRandom().nextInt());
private static final Joiner KEY_JOINER = Joiner.on("-");
public static String generate(Object... args){
String hexIdString =
ByteUtil.toHexString(toByteArray(Objects.hash(args), MachineUtil.getMachineIdentifier(),
counter.incrementAndGet()));
return KEY_JOINER.join(TIMESTAMP_FORMAT.format(new Date()), hexIdString);
}
/**
* Concat machine id, counter and key to byte array
* Only retrieve lower 3 bytes of the id and counter and 2 bytes of the keyHashCode
*/
protected static byte[] toByteArray(int keyHashCode, int machineIdentifier, int counter) {
byte[] bytes = new byte[8];
bytes[0] = ByteUtil.int1(keyHashCode);
bytes[1] = ByteUtil.int0(keyHashCode);
bytes[2] = ByteUtil.int2(machineIdentifier);
bytes[3] = ByteUtil.int1(machineIdentifier);
bytes[4] = ByteUtil.int0(machineIdentifier);
bytes[5] = ByteUtil.int2(counter);
bytes[6] = ByteUtil.int1(counter);
bytes[7] = ByteUtil.int0(counter);
return bytes;
}
}
package com.ctrip.framework.apollo.configservice; package com.ctrip.framework.apollo.configservice;
import com.ctrip.framework.apollo.biz.grayReleaseRule.GrayReleaseRulesHolder;
import com.ctrip.framework.apollo.biz.message.ReleaseMessageScanner; import com.ctrip.framework.apollo.biz.message.ReleaseMessageScanner;
import com.ctrip.framework.apollo.configservice.controller.ConfigFileController; import com.ctrip.framework.apollo.configservice.controller.ConfigFileController;
import com.ctrip.framework.apollo.configservice.controller.NotificationController; import com.ctrip.framework.apollo.configservice.controller.NotificationController;
...@@ -14,21 +15,34 @@ import org.springframework.context.annotation.Configuration; ...@@ -14,21 +15,34 @@ import org.springframework.context.annotation.Configuration;
*/ */
@Configuration @Configuration
public class ConfigServiceAutoConfiguration { public class ConfigServiceAutoConfiguration {
@Bean
public GrayReleaseRulesHolder grayReleaseRulesHolder() {
return new GrayReleaseRulesHolder();
}
@Configuration
static class MessageScannerConfiguration {
@Autowired @Autowired
private NotificationController notificationController; private NotificationController notificationController;
@Autowired @Autowired
private ConfigFileController configFileController; private ConfigFileController configFileController;
@Autowired @Autowired
private NotificationControllerV2 notificationControllerV2; private NotificationControllerV2 notificationControllerV2;
@Autowired
private GrayReleaseRulesHolder grayReleaseRulesHolder;
@Bean @Bean
public ReleaseMessageScanner releaseMessageScanner() { public ReleaseMessageScanner releaseMessageScanner() {
ReleaseMessageScanner releaseMessageScanner = new ReleaseMessageScanner(); ReleaseMessageScanner releaseMessageScanner = new ReleaseMessageScanner();
//handle server cache first //1. handle gray release rule
releaseMessageScanner.addMessageListener(grayReleaseRulesHolder);
//2. handle server cache
releaseMessageScanner.addMessageListener(configFileController); releaseMessageScanner.addMessageListener(configFileController);
//3. notify clients
releaseMessageScanner.addMessageListener(notificationControllerV2); releaseMessageScanner.addMessageListener(notificationControllerV2);
releaseMessageScanner.addMessageListener(notificationController); releaseMessageScanner.addMessageListener(notificationController);
return releaseMessageScanner; return releaseMessageScanner;
} }
}
} }
...@@ -10,6 +10,7 @@ import com.google.gson.Gson; ...@@ -10,6 +10,7 @@ import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.biz.entity.Release; import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.grayReleaseRule.GrayReleaseRulesHolder;
import com.ctrip.framework.apollo.biz.service.AppNamespaceService; import com.ctrip.framework.apollo.biz.service.AppNamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService; import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.common.entity.AppNamespace; import com.ctrip.framework.apollo.common.entity.AppNamespace;
...@@ -51,6 +52,8 @@ public class ConfigController { ...@@ -51,6 +52,8 @@ public class ConfigController {
private NamespaceUtil namespaceUtil; private NamespaceUtil namespaceUtil;
@Autowired @Autowired
private InstanceConfigAuditUtil instanceConfigAuditUtil; private InstanceConfigAuditUtil instanceConfigAuditUtil;
@Autowired
private GrayReleaseRulesHolder grayReleaseRulesHolder;
private static final Gson gson = new Gson(); private static final Gson gson = new Gson();
private static final Type configurationTypeReference = private static final Type configurationTypeReference =
...@@ -80,7 +83,8 @@ public class ConfigController { ...@@ -80,7 +83,8 @@ public class ConfigController {
String appClusterNameLoaded = clusterName; String appClusterNameLoaded = clusterName;
if (!ConfigConsts.NO_APPID_PLACEHOLDER.equalsIgnoreCase(appId)) { if (!ConfigConsts.NO_APPID_PLACEHOLDER.equalsIgnoreCase(appId)) {
Release currentAppRelease = loadConfig(appId, clusterName, namespace, dataCenter); Release currentAppRelease = loadConfig(appId, clientIp, appId, clusterName, namespace,
dataCenter);
if (currentAppRelease != null) { if (currentAppRelease != null) {
releases.add(currentAppRelease); releases.add(currentAppRelease);
...@@ -91,7 +95,8 @@ public class ConfigController { ...@@ -91,7 +95,8 @@ public class ConfigController {
//if namespace does not belong to this appId, should check if there is a public configuration //if namespace does not belong to this appId, should check if there is a public configuration
if (!namespaceBelongsToAppId(appId, namespace)) { if (!namespaceBelongsToAppId(appId, namespace)) {
Release publicRelease = this.findPublicConfig(appId, clusterName, namespace, dataCenter); Release publicRelease = this.findPublicConfig(appId, clientIp, clusterName, namespace,
dataCenter);
if (!Objects.isNull(publicRelease)) { if (!Objects.isNull(publicRelease)) {
releases.add(publicRelease); releases.add(publicRelease);
} }
...@@ -146,30 +151,31 @@ public class ConfigController { ...@@ -146,30 +151,31 @@ public class ConfigController {
} }
/** /**
* @param applicationId the application which uses public config * @param clientAppId the application which uses public config
* @param namespace the namespace * @param namespace the namespace
* @param dataCenter the datacenter * @param dataCenter the datacenter
*/ */
private Release findPublicConfig(String applicationId, String clusterName, String namespace, private Release findPublicConfig(String clientAppId, String clientIp, String clusterName,
String namespace,
String dataCenter) { String dataCenter) {
AppNamespace appNamespace = appNamespaceService.findPublicNamespaceByName(namespace); AppNamespace appNamespace = appNamespaceService.findPublicNamespaceByName(namespace);
//check whether the namespace's appId equals to current one //check whether the namespace's appId equals to current one
if (Objects.isNull(appNamespace) || Objects.equals(applicationId, appNamespace.getAppId())) { if (Objects.isNull(appNamespace) || Objects.equals(clientAppId, appNamespace.getAppId())) {
return null; return null;
} }
String publicConfigAppId = appNamespace.getAppId(); String publicConfigAppId = appNamespace.getAppId();
return loadConfig(publicConfigAppId, clusterName, namespace, dataCenter); return loadConfig(clientAppId, clientIp, publicConfigAppId, clusterName, namespace, dataCenter);
} }
private Release loadConfig(String appId, String clusterName, String namespace, String private Release loadConfig(String clientAppId, String clientIp, String configAppId, String
dataCenter) { configClusterName, String configNamespace, String dataCenter) {
//load from specified cluster fist //load from specified cluster fist
if (!Objects.equals(ConfigConsts.CLUSTER_NAME_DEFAULT, clusterName)) { if (!Objects.equals(ConfigConsts.CLUSTER_NAME_DEFAULT, configClusterName)) {
Release clusterRelease = Release clusterRelease = findRelease(clientAppId, clientIp, configAppId, configClusterName,
releaseService.findLatestActiveRelease(appId, clusterName, namespace); configNamespace);
if (!Objects.isNull(clusterRelease)) { if (!Objects.isNull(clusterRelease)) {
return clusterRelease; return clusterRelease;
...@@ -177,17 +183,36 @@ public class ConfigController { ...@@ -177,17 +183,36 @@ public class ConfigController {
} }
//try to load via data center //try to load via data center
if (!Strings.isNullOrEmpty(dataCenter) && !Objects.equals(dataCenter, clusterName)) { if (!Strings.isNullOrEmpty(dataCenter) && !Objects.equals(dataCenter, configClusterName)) {
Release dataCenterRelease = Release dataCenterRelease = findRelease(clientAppId, clientIp, configAppId, dataCenter,
releaseService.findLatestActiveRelease(appId, dataCenter, namespace); configNamespace);
if (!Objects.isNull(dataCenterRelease)) { if (!Objects.isNull(dataCenterRelease)) {
return dataCenterRelease; return dataCenterRelease;
} }
} }
//fallback to default release //fallback to default release
return releaseService return findRelease(clientAppId, clientIp, configAppId, ConfigConsts.CLUSTER_NAME_DEFAULT,
.findLatestActiveRelease(appId, ConfigConsts.CLUSTER_NAME_DEFAULT, namespace); configNamespace);
}
private Release findRelease(String clientAppId, String clientIp, String configAppId, String
configClusterName, String configNamespace) {
Long grayReleaseId = grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule(clientAppId,
clientIp, configAppId, configClusterName, configNamespace);
Release release = null;
if (grayReleaseId != null) {
release = releaseService.findActiveOne(grayReleaseId);
}
if (release == null) {
release = releaseService.findLatestActiveRelease(configAppId, configClusterName,
configNamespace);
}
return release;
} }
/** /**
...@@ -217,7 +242,8 @@ public class ConfigController { ...@@ -217,7 +242,8 @@ public class ConfigController {
return; return;
} }
for (Release release : releases) { for (Release release : releases) {
instanceConfigAuditUtil.audit(appId, cluster, datacenter, clientIp, release.getAppId(), release.getClusterName(), instanceConfigAuditUtil.audit(appId, cluster, datacenter, clientIp, release.getAppId(),
release.getClusterName(),
release.getNamespaceName(), release.getReleaseKey()); release.getNamespaceName(), release.getReleaseKey());
} }
} }
......
package com.ctrip.framework.apollo.configservice.controller; package com.ctrip.framework.apollo.configservice.controller;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
...@@ -14,6 +15,7 @@ import com.google.common.collect.Multimaps; ...@@ -14,6 +15,7 @@ import com.google.common.collect.Multimaps;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage; import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.grayReleaseRule.GrayReleaseRulesHolder;
import com.ctrip.framework.apollo.biz.message.ReleaseMessageListener; import com.ctrip.framework.apollo.biz.message.ReleaseMessageListener;
import com.ctrip.framework.apollo.biz.message.Topics; import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtil; import com.ctrip.framework.apollo.configservice.util.NamespaceUtil;
...@@ -53,6 +55,8 @@ import javax.servlet.http.HttpServletResponse; ...@@ -53,6 +55,8 @@ import javax.servlet.http.HttpServletResponse;
public class ConfigFileController implements ReleaseMessageListener { public class ConfigFileController implements ReleaseMessageListener {
private static final Logger logger = LoggerFactory.getLogger(ConfigFileController.class); private static final Logger logger = LoggerFactory.getLogger(ConfigFileController.class);
private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR); private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
private static final Splitter X_FORWARDED_FOR_SPLITTER = Splitter.on(",").omitEmptyStrings()
.trimResults();
private static final long MAX_CACHE_SIZE = 50 * 1024 * 1024; // 50MB private static final long MAX_CACHE_SIZE = 50 * 1024 * 1024; // 50MB
private static final long EXPIRE_AFTER_WRITE = 30; private static final long EXPIRE_AFTER_WRITE = 30;
private final HttpHeaders propertiesResponseHeaders; private final HttpHeaders propertiesResponseHeaders;
...@@ -74,6 +78,9 @@ public class ConfigFileController implements ReleaseMessageListener { ...@@ -74,6 +78,9 @@ public class ConfigFileController implements ReleaseMessageListener {
@Autowired @Autowired
private WatchKeysUtil watchKeysUtil; private WatchKeysUtil watchKeysUtil;
@Autowired
private GrayReleaseRulesHolder grayReleaseRulesHolder;
public ConfigFileController() { public ConfigFileController() {
localCache = CacheBuilder.newBuilder() localCache = CacheBuilder.newBuilder()
.expireAfterWrite(EXPIRE_AFTER_WRITE, TimeUnit.MINUTES) .expireAfterWrite(EXPIRE_AFTER_WRITE, TimeUnit.MINUTES)
...@@ -157,31 +164,41 @@ public class ConfigFileController implements ReleaseMessageListener { ...@@ -157,31 +164,41 @@ public class ConfigFileController implements ReleaseMessageListener {
//strip out .properties suffix //strip out .properties suffix
namespace = namespaceUtil.filterNamespaceName(namespace); namespace = namespaceUtil.filterNamespaceName(namespace);
//TODO add clientIp as key parts? if (Strings.isNullOrEmpty(clientIp)) {
clientIp = tryToGetClientIp(request);
}
//1. check whether this client has gray release rules
boolean hasGrayReleaseRule = grayReleaseRulesHolder.hasGrayReleaseRule(appId, clientIp,
namespace);
String cacheKey = assembleCacheKey(outputFormat, appId, clusterName, namespace, dataCenter); String cacheKey = assembleCacheKey(outputFormat, appId, clusterName, namespace, dataCenter);
//2. try to load gray release and return
if (hasGrayReleaseRule) {
Cat.logEvent("ConfigFile.Cache.GrayRelease", cacheKey);
return loadConfig(outputFormat, appId, clusterName, namespace, dataCenter, clientIp,
request, response);
}
//3. if not gray release, check weather cache exists, if exists, return
String result = localCache.getIfPresent(cacheKey); String result = localCache.getIfPresent(cacheKey);
//4. if not exists, load from ConfigController
if (Strings.isNullOrEmpty(result)) { if (Strings.isNullOrEmpty(result)) {
Cat.logEvent("ConfigFile.Cache.Miss", cacheKey); Cat.logEvent("ConfigFile.Cache.Miss", cacheKey);
ApolloConfig apolloConfig = result = loadConfig(outputFormat, appId, clusterName, namespace, dataCenter, clientIp,
configController request, response);
.queryConfig(appId, clusterName, namespace, dataCenter, "-1", clientIp, request,
response);
if (apolloConfig == null || apolloConfig.getConfigurations() == null) { if (result == null) {
return null; return null;
} }
//5. Double check if this client needs to load gray release, if yes, load from db again
switch (outputFormat) { //This step is mainly to avoid cache pollution
case PROPERTIES: if (grayReleaseRulesHolder.hasGrayReleaseRule(appId, clientIp, namespace)) {
Properties properties = new Properties(); Cat.logEvent("ConfigFile.Cache.GrayReleaseConflict", cacheKey);
properties.putAll(apolloConfig.getConfigurations()); return loadConfig(outputFormat, appId, clusterName, namespace, dataCenter, clientIp,
result = PropertiesUtil.toString(properties); request, response);
break;
case JSON:
result = gson.toJson(apolloConfig.getConfigurations());
break;
} }
localCache.put(cacheKey, result); localCache.put(cacheKey, result);
...@@ -203,6 +220,33 @@ public class ConfigFileController implements ReleaseMessageListener { ...@@ -203,6 +220,33 @@ public class ConfigFileController implements ReleaseMessageListener {
return result; return result;
} }
private String loadConfig(ConfigFileOutputFormat outputFormat, String appId, String clusterName,
String namespace, String dataCenter, String clientIp,
HttpServletRequest request,
HttpServletResponse response) throws IOException {
ApolloConfig apolloConfig = configController.queryConfig(appId, clusterName, namespace,
dataCenter, "-1", clientIp, request, response);
if (apolloConfig == null || apolloConfig.getConfigurations() == null) {
return null;
}
String result = null;
switch (outputFormat) {
case PROPERTIES:
Properties properties = new Properties();
properties.putAll(apolloConfig.getConfigurations());
result = PropertiesUtil.toString(properties);
break;
case JSON:
result = gson.toJson(apolloConfig.getConfigurations());
break;
}
return result;
}
String assembleCacheKey(ConfigFileOutputFormat outputFormat, String appId, String clusterName, String assembleCacheKey(ConfigFileOutputFormat outputFormat, String appId, String clusterName,
String namespace, String namespace,
String dataCenter) { String dataCenter) {
...@@ -249,4 +293,12 @@ public class ConfigFileController implements ReleaseMessageListener { ...@@ -249,4 +293,12 @@ public class ConfigFileController implements ReleaseMessageListener {
return value; return value;
} }
} }
private String tryToGetClientIp(HttpServletRequest request) {
String forwardedFor = request.getHeader("X-FORWARDED-FOR");
if (!Strings.isNullOrEmpty(forwardedFor)) {
return X_FORWARDED_FOR_SPLITTER.splitToList(forwardedFor).get(0);
}
return request.getRemoteAddr();
}
} }
...@@ -74,7 +74,7 @@ public class InstanceConfigAuditUtil implements InitializingBean { ...@@ -74,7 +74,7 @@ public class InstanceConfigAuditUtil implements InitializingBean {
//load instance config release key from cache, and check if release key is the same //load instance config release key from cache, and check if release key is the same
String instanceConfigCacheKey = assembleInstanceConfigKey(instanceId, auditModel String instanceConfigCacheKey = assembleInstanceConfigKey(instanceId, auditModel
.getConfigAppId(), auditModel.getConfigClusterName(), auditModel.getConfigNamespace()); .getConfigAppId(), auditModel.getConfigNamespace());
String cacheReleaseKey = instanceConfigReleaseKeyCache.getIfPresent(instanceConfigCacheKey); String cacheReleaseKey = instanceConfigReleaseKeyCache.getIfPresent(instanceConfigCacheKey);
//if release key is the same, then skip audit //if release key is the same, then skip audit
...@@ -86,10 +86,11 @@ public class InstanceConfigAuditUtil implements InitializingBean { ...@@ -86,10 +86,11 @@ public class InstanceConfigAuditUtil implements InitializingBean {
//if release key is not the same or cannot find in cache, then do audit //if release key is not the same or cannot find in cache, then do audit
InstanceConfig instanceConfig = instanceService.findInstanceConfig(instanceId, auditModel InstanceConfig instanceConfig = instanceService.findInstanceConfig(instanceId, auditModel
.getConfigAppId(), auditModel.getConfigClusterName(), auditModel.getConfigNamespace()); .getConfigAppId(), auditModel.getConfigNamespace());
if (instanceConfig != null) { if (instanceConfig != null) {
if (!Objects.equals(instanceConfig.getReleaseKey(), auditModel.getReleaseKey())) { if (!Objects.equals(instanceConfig.getReleaseKey(), auditModel.getReleaseKey())) {
instanceConfig.setConfigClusterName(auditModel.getConfigClusterName());
instanceConfig.setReleaseKey(auditModel.getReleaseKey()); instanceConfig.setReleaseKey(auditModel.getReleaseKey());
instanceConfig.setReleaseDeliveryTime(auditModel.getOfferTime()); instanceConfig.setReleaseDeliveryTime(auditModel.getOfferTime());
} }
...@@ -164,10 +165,8 @@ public class InstanceConfigAuditUtil implements InitializingBean { ...@@ -164,10 +165,8 @@ public class InstanceConfigAuditUtil implements InitializingBean {
return STRING_JOINER.join(keyParts); return STRING_JOINER.join(keyParts);
} }
private String assembleInstanceConfigKey(long instanceId, String configAppId, String private String assembleInstanceConfigKey(long instanceId, String configAppId, String configNamespace) {
configClusterName, return STRING_JOINER.join(instanceId, configAppId, configNamespace);
String configNamespace) {
return STRING_JOINER.join(instanceId, configAppId, configClusterName, configNamespace);
} }
public static class InstanceConfigAuditModel { public static class InstanceConfigAuditModel {
......
...@@ -6,6 +6,7 @@ import com.google.common.collect.Lists; ...@@ -6,6 +6,7 @@ import com.google.common.collect.Lists;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import com.ctrip.framework.apollo.biz.grayReleaseRule.GrayReleaseRulesHolder;
import com.ctrip.framework.apollo.biz.service.ReleaseService; import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.common.entity.AppNamespace; import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.biz.entity.Release; import com.ctrip.framework.apollo.biz.entity.Release;
...@@ -63,6 +64,8 @@ public class ConfigControllerTest { ...@@ -63,6 +64,8 @@ public class ConfigControllerTest {
@Mock @Mock
private InstanceConfigAuditUtil instanceConfigAuditUtil; private InstanceConfigAuditUtil instanceConfigAuditUtil;
@Mock @Mock
private GrayReleaseRulesHolder grayReleaseRulesHolder;
@Mock
private HttpServletRequest someRequest; private HttpServletRequest someRequest;
@Before @Before
...@@ -72,6 +75,7 @@ public class ConfigControllerTest { ...@@ -72,6 +75,7 @@ public class ConfigControllerTest {
ReflectionTestUtils.setField(configController, "appNamespaceService", appNamespaceService); ReflectionTestUtils.setField(configController, "appNamespaceService", appNamespaceService);
ReflectionTestUtils.setField(configController, "namespaceUtil", namespaceUtil); ReflectionTestUtils.setField(configController, "namespaceUtil", namespaceUtil);
ReflectionTestUtils.setField(configController, "instanceConfigAuditUtil", instanceConfigAuditUtil); ReflectionTestUtils.setField(configController, "instanceConfigAuditUtil", instanceConfigAuditUtil);
ReflectionTestUtils.setField(configController, "grayReleaseRulesHolder", grayReleaseRulesHolder);
someAppId = "1"; someAppId = "1";
someClusterName = "someClusterName"; someClusterName = "someClusterName";
...@@ -90,6 +94,8 @@ public class ConfigControllerTest { ...@@ -90,6 +94,8 @@ public class ConfigControllerTest {
when(namespaceUtil.filterNamespaceName(defaultNamespaceName)).thenReturn(defaultNamespaceName); when(namespaceUtil.filterNamespaceName(defaultNamespaceName)).thenReturn(defaultNamespaceName);
when(namespaceUtil.filterNamespaceName(somePublicNamespaceName)) when(namespaceUtil.filterNamespaceName(somePublicNamespaceName))
.thenReturn(somePublicNamespaceName); .thenReturn(somePublicNamespaceName);
when(grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule(anyString(), anyString(),
anyString(), anyString(), anyString())).thenReturn(null);
} }
@Test @Test
...@@ -116,6 +122,45 @@ public class ConfigControllerTest { ...@@ -116,6 +122,45 @@ public class ConfigControllerTest {
someClientIp, someAppId, someClusterName, defaultNamespaceName, someServerSideNewReleaseKey); someClientIp, someAppId, someClusterName, defaultNamespaceName, someServerSideNewReleaseKey);
} }
@Test
public void testQueryConfigWithGrayRelease() throws Exception {
String someClientSideReleaseKey = "1";
String someServerSideNewReleaseKey = "2";
String someServerSideGrayReleaseKey = "3";
HttpServletResponse someResponse = mock(HttpServletResponse.class);
Release grayRelease = mock(Release.class);
long grayReleaseId = 999;
String someGrayConfiguration = "{\"apollo.bar\": \"foo_gray\"}";
when(grayReleaseRulesHolder.findReleaseIdFromGrayReleaseRule(someAppId, someClientIp,
someAppId, someClusterName, defaultNamespaceName)).thenReturn(grayReleaseId);
when(releaseService.findActiveOne(grayReleaseId)).thenReturn(grayRelease);
when(releaseService.findLatestActiveRelease(someAppId, someClusterName, defaultNamespaceName))
.thenReturn(someRelease);
when(someRelease.getReleaseKey()).thenReturn(someServerSideNewReleaseKey);
when(grayRelease.getAppId()).thenReturn(someAppId);
when(grayRelease.getClusterName()).thenReturn(someClusterName);
when(grayRelease.getReleaseKey()).thenReturn(someServerSideGrayReleaseKey);
when(grayRelease.getNamespaceName()).thenReturn(defaultNamespaceName);
when(grayRelease.getConfigurations()).thenReturn(someGrayConfiguration);
ApolloConfig result = configController.queryConfig(someAppId, someClusterName,
defaultNamespaceName, someDataCenter, someClientSideReleaseKey,
someClientIp, someRequest, someResponse);
verify(releaseService, times(1)).findActiveOne(grayReleaseId);
verify(releaseService, never()).findLatestActiveRelease(someAppId, someClusterName, defaultNamespaceName);
assertEquals(someAppId, result.getAppId());
assertEquals(someClusterName, result.getCluster());
assertEquals(defaultNamespaceName, result.getNamespaceName());
assertEquals(someServerSideGrayReleaseKey, result.getReleaseKey());
verify(instanceConfigAuditUtil, times(1)).audit(someAppId, someClusterName, someDataCenter,
someClientIp, someAppId, someClusterName, defaultNamespaceName, someServerSideGrayReleaseKey);
}
@Test @Test
public void testQueryConfigFile() throws Exception { public void testQueryConfigFile() throws Exception {
String someClientSideReleaseKey = "1"; String someClientSideReleaseKey = "1";
......
...@@ -9,6 +9,7 @@ import com.google.common.reflect.TypeToken; ...@@ -9,6 +9,7 @@ import com.google.common.reflect.TypeToken;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage; import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.grayReleaseRule.GrayReleaseRulesHolder;
import com.ctrip.framework.apollo.biz.message.Topics; import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtil; import com.ctrip.framework.apollo.configservice.util.NamespaceUtil;
import com.ctrip.framework.apollo.configservice.util.WatchKeysUtil; import com.ctrip.framework.apollo.configservice.util.WatchKeysUtil;
...@@ -33,6 +34,7 @@ import javax.servlet.http.HttpServletResponse; ...@@ -33,6 +34,7 @@ import javax.servlet.http.HttpServletResponse;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
...@@ -49,6 +51,8 @@ public class ConfigFileControllerTest { ...@@ -49,6 +51,8 @@ public class ConfigFileControllerTest {
private WatchKeysUtil watchKeysUtil; private WatchKeysUtil watchKeysUtil;
@Mock @Mock
private NamespaceUtil namespaceUtil; private NamespaceUtil namespaceUtil;
@Mock
private GrayReleaseRulesHolder grayReleaseRulesHolder;
private ConfigFileController configFileController; private ConfigFileController configFileController;
private String someAppId; private String someAppId;
private String someClusterName; private String someClusterName;
...@@ -68,6 +72,7 @@ public class ConfigFileControllerTest { ...@@ -68,6 +72,7 @@ public class ConfigFileControllerTest {
ReflectionTestUtils.setField(configFileController, "configController", configController); ReflectionTestUtils.setField(configFileController, "configController", configController);
ReflectionTestUtils.setField(configFileController, "watchKeysUtil", watchKeysUtil); ReflectionTestUtils.setField(configFileController, "watchKeysUtil", watchKeysUtil);
ReflectionTestUtils.setField(configFileController, "namespaceUtil", namespaceUtil); ReflectionTestUtils.setField(configFileController, "namespaceUtil", namespaceUtil);
ReflectionTestUtils.setField(configFileController, "grayReleaseRulesHolder", grayReleaseRulesHolder);
someAppId = "someAppId"; someAppId = "someAppId";
someClusterName = "someClusterName"; someClusterName = "someClusterName";
...@@ -76,6 +81,8 @@ public class ConfigFileControllerTest { ...@@ -76,6 +81,8 @@ public class ConfigFileControllerTest {
someClientIp = "10.1.1.1"; someClientIp = "10.1.1.1";
when(namespaceUtil.filterNamespaceName(someNamespace)).thenReturn(someNamespace); when(namespaceUtil.filterNamespaceName(someNamespace)).thenReturn(someNamespace);
when(grayReleaseRulesHolder.hasGrayReleaseRule(anyString(), anyString(), anyString()))
.thenReturn(false);
watchedKeys2CacheKey = watchedKeys2CacheKey =
(Multimap<String, String>) ReflectionTestUtils (Multimap<String, String>) ReflectionTestUtils
...@@ -169,6 +176,45 @@ public class ConfigFileControllerTest { ...@@ -169,6 +176,45 @@ public class ConfigFileControllerTest {
assertEquals(configurations, gson.fromJson(response.getBody(), responseType)); assertEquals(configurations, gson.fromJson(response.getBody(), responseType));
} }
@Test
public void testQueryConfigWithGrayRelease() throws Exception {
String someKey = "someKey";
String someValue = "someValue";
Gson gson = new Gson();
Type responseType = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> configurations =
ImmutableMap.of(someKey, someValue);
when(grayReleaseRulesHolder.hasGrayReleaseRule(someAppId, someClientIp, someNamespace))
.thenReturn(true);
ApolloConfig someApolloConfig = mock(ApolloConfig.class);
when(someApolloConfig.getConfigurations()).thenReturn(configurations);
when(configController
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
someRequest, someResponse)).thenReturn(someApolloConfig);
ResponseEntity<String> response =
configFileController
.queryConfigAsJson(someAppId, someClusterName, someNamespace, someDataCenter,
someClientIp, someRequest, someResponse);
ResponseEntity<String> anotherResponse =
configFileController
.queryConfigAsJson(someAppId, someClusterName, someNamespace, someDataCenter,
someClientIp, someRequest, someResponse);
verify(configController, times(2))
.queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
someRequest, someResponse);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(configurations, gson.fromJson(response.getBody(), responseType));
assertTrue(watchedKeys2CacheKey.isEmpty());
assertTrue(cacheKey2WatchedKeys.isEmpty());
}
@Test @Test
public void testHandleMessage() throws Exception { public void testHandleMessage() throws Exception {
String someWatchKey = "someWatchKey"; String someWatchKey = "someWatchKey";
......
package com.ctrip.framework.apollo.configservice.integration; package com.ctrip.framework.apollo.configservice.integration;
import com.google.common.base.Joiner;
import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfig; import com.ctrip.framework.apollo.core.dto.ApolloConfig;
...@@ -10,6 +12,11 @@ import org.springframework.http.ResponseEntity; ...@@ -10,6 +12,11 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql;
import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.HttpStatusCodeException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
/** /**
...@@ -17,20 +24,26 @@ import static org.junit.Assert.assertEquals; ...@@ -17,20 +24,26 @@ import static org.junit.Assert.assertEquals;
*/ */
public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest { public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest {
private String someAppId; private String someAppId;
private String somePublicAppId;
private String someCluster; private String someCluster;
private String someNamespace; private String someNamespace;
private String somePublicNamespace; private String somePublicNamespace;
private String someDC; private String someDC;
private String someDefaultCluster; private String someDefaultCluster;
private String someClientIp;
private ExecutorService executorService;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
someAppId = "someAppId"; someAppId = "someAppId";
someCluster = "someCluster"; someCluster = "someCluster";
someNamespace = "someNamespace"; someNamespace = "someNamespace";
somePublicAppId = "somePublicAppId";
somePublicNamespace = "somePublicNamespace"; somePublicNamespace = "somePublicNamespace";
someDC = "someDC"; someDC = "someDC";
someDefaultCluster = ConfigConsts.CLUSTER_NAME_DEFAULT; someDefaultCluster = ConfigConsts.CLUSTER_NAME_DEFAULT;
someClientIp = "1.1.1.1";
executorService = Executors.newSingleThreadExecutor();
} }
@Test @Test
...@@ -47,6 +60,29 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest ...@@ -47,6 +60,29 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals("v1", result.getConfigurations().get("k1")); assertEquals("v1", result.getConfigurations().get("k1"));
} }
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-gray-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryGrayConfigWithDefaultClusterAndDefaultNamespaceOK() throws Exception {
AtomicBoolean stop = new AtomicBoolean();
periodicSendMessage(executorService, assembleKey(someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, ConfigConsts.NAMESPACE_APPLICATION),
stop);
TimeUnit.MILLISECONDS.sleep(500);
stop.set(true);
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?ip={clientIp}", ApolloConfig.class,
getHostUrl(), someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, ConfigConsts.NAMESPACE_APPLICATION, someClientIp);
ApolloConfig result = response.getBody();
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("TEST-GRAY-RELEASE-KEY1", result.getReleaseKey());
assertEquals("v1-gray", result.getConfigurations().get("k1"));
}
@Test @Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
...@@ -119,6 +155,30 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest ...@@ -119,6 +155,30 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode()); assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode());
} }
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-gray-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryPublicGrayConfigWithNoOverride() throws Exception {
AtomicBoolean stop = new AtomicBoolean();
periodicSendMessage(executorService, assembleKey(somePublicAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, somePublicNamespace),
stop);
TimeUnit.MILLISECONDS.sleep(500);
stop.set(true);
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?ip={clientIp}", ApolloConfig.class,
getHostUrl(), someAppId, someCluster, somePublicNamespace, someClientIp);
ApolloConfig result = response.getBody();
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals("TEST-GRAY-RELEASE-KEY2", result.getReleaseKey());
assertEquals("gray-v1", result.getConfigurations().get("k1"));
assertEquals("gray-v2", result.getConfigurations().get("k2"));
}
@Test @Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
...@@ -199,6 +259,33 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest ...@@ -199,6 +259,33 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals("default-v2", result.getConfigurations().get("k2")); assertEquals("default-v2", result.getConfigurations().get("k2"));
} }
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-release-public-default-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-gray-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryPublicGrayConfigWithOverride() throws Exception {
AtomicBoolean stop = new AtomicBoolean();
periodicSendMessage(executorService, assembleKey(somePublicAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, somePublicNamespace),
stop);
TimeUnit.MILLISECONDS.sleep(500);
stop.set(true);
ResponseEntity<ApolloConfig> response = restTemplate
.getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?ip={clientIp}", ApolloConfig.class,
getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, someClientIp);
ApolloConfig result = response.getBody();
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(
"TEST-RELEASE-KEY5" + ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR + "TEST-GRAY-RELEASE-KEY2",
result.getReleaseKey());
assertEquals("override-v1", result.getConfigurations().get("k1"));
assertEquals("gray-v2", result.getConfigurations().get("k2"));
}
@Test @Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
...@@ -250,4 +337,8 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest ...@@ -250,4 +337,8 @@ public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest
assertEquals("someDC-v1", result.getConfigurations().get("k1")); assertEquals("someDC-v1", result.getConfigurations().get("k1"));
assertEquals("someDC-v2", result.getConfigurations().get("k2")); assertEquals("someDC-v2", result.getConfigurations().get("k2"));
} }
private String assembleKey(String appId, String cluster, String namespace) {
return Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join(appId, cluster, namespace);
}
} }
package com.ctrip.framework.apollo.configservice.integration; package com.ctrip.framework.apollo.configservice.integration;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.reflect.TypeToken; import com.google.common.reflect.TypeToken;
...@@ -18,9 +19,13 @@ import org.springframework.test.context.jdbc.Sql; ...@@ -18,9 +19,13 @@ import org.springframework.test.context.jdbc.Sql;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
...@@ -28,22 +33,30 @@ import static org.junit.Assert.assertTrue; ...@@ -28,22 +33,30 @@ import static org.junit.Assert.assertTrue;
*/ */
public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegrationTest { public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegrationTest {
private String someAppId; private String someAppId;
private String somePublicAppId;
private String someCluster; private String someCluster;
private String someNamespace; private String someNamespace;
private String somePublicNamespace; private String somePublicNamespace;
private String someDC; private String someDC;
private String someDefaultCluster; private String someDefaultCluster;
private String grayClientIp;
private String nonGrayClientIp;
private Gson gson = new Gson(); private Gson gson = new Gson();
private ExecutorService executorService;
private Type mapResponseType = new TypeToken<Map<String, String>>(){}.getType(); private Type mapResponseType = new TypeToken<Map<String, String>>(){}.getType();
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
someDefaultCluster = ConfigConsts.CLUSTER_NAME_DEFAULT; someDefaultCluster = ConfigConsts.CLUSTER_NAME_DEFAULT;
someAppId = "someAppId"; someAppId = "someAppId";
somePublicAppId = "somePublicAppId";
someCluster = "someCluster"; someCluster = "someCluster";
someNamespace = "someNamespace"; someNamespace = "someNamespace";
somePublicNamespace = "somePublicNamespace"; somePublicNamespace = "somePublicNamespace";
someDC = "someDC"; someDC = "someDC";
grayClientIp = "1.1.1.1";
nonGrayClientIp = "2.2.2.2";
executorService = Executors.newSingleThreadExecutor();
} }
@Test @Test
...@@ -61,6 +74,40 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration ...@@ -61,6 +74,40 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
assertTrue(result.contains("k2=v2")); assertTrue(result.contains("k2=v2"));
} }
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-gray-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryConfigAsPropertiesWithGrayRelease() throws Exception {
AtomicBoolean stop = new AtomicBoolean();
periodicSendMessage(executorService, assembleKey(someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, ConfigConsts.NAMESPACE_APPLICATION),
stop);
TimeUnit.MILLISECONDS.sleep(500);
stop.set(true);
ResponseEntity<String> response =
restTemplate
.getForEntity("{baseurl}/configfiles/{appId}/{clusterName}/{namespace}?ip={clientIp}", String.class,
getHostUrl(), someAppId, someDefaultCluster, ConfigConsts.NAMESPACE_APPLICATION, grayClientIp);
ResponseEntity<String> anotherResponse =
restTemplate
.getForEntity("{baseurl}/configfiles/{appId}/{clusterName}/{namespace}?ip={clientIp}", String.class,
getHostUrl(), someAppId, someDefaultCluster, ConfigConsts.NAMESPACE_APPLICATION, nonGrayClientIp);
String result = response.getBody();
String anotherResult = anotherResponse.getBody();
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(result.contains("k1=v1-gray"));
assertEquals(HttpStatus.OK, anotherResponse.getStatusCode());
assertFalse(anotherResult.contains("k1=v1-gray"));
assertTrue(anotherResult.contains("k1=v1"));
}
@Test @Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-release-public-dc-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/integration-test/test-release-public-dc-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
...@@ -89,7 +136,6 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration ...@@ -89,7 +136,6 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
.getForEntity("{baseurl}/configfiles/json/{appId}/{clusterName}/{namespace}", String.class, .getForEntity("{baseurl}/configfiles/json/{appId}/{clusterName}/{namespace}", String.class,
getHostUrl(), someAppId, someCluster, someNamespace); getHostUrl(), someAppId, someCluster, someNamespace);
String result = response.getBody();
Map<String, String> configs = gson.fromJson(response.getBody(), mapResponseType); Map<String, String> configs = gson.fromJson(response.getBody(), mapResponseType);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
...@@ -108,7 +154,6 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration ...@@ -108,7 +154,6 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
String.class, String.class,
getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, someDC); getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, someDC);
String result = response.getBody();
Map<String, String> configs = gson.fromJson(response.getBody(), mapResponseType); Map<String, String> configs = gson.fromJson(response.getBody(), mapResponseType);
assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(HttpStatus.OK, response.getStatusCode());
...@@ -116,6 +161,47 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration ...@@ -116,6 +161,47 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
assertEquals("someDC-v2", configs.get("k2")); assertEquals("someDC-v2", configs.get("k2"));
} }
@Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-release-public-default-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/test-gray-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testQueryPublicConfigAsJsonWithGrayRelease() throws Exception {
AtomicBoolean stop = new AtomicBoolean();
periodicSendMessage(executorService, assembleKey(somePublicAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, somePublicNamespace),
stop);
TimeUnit.MILLISECONDS.sleep(500);
stop.set(true);
ResponseEntity<String> response =
restTemplate
.getForEntity(
"{baseurl}/configfiles/json/{appId}/{clusterName}/{namespace}?ip={clientIp}",
String.class,
getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, grayClientIp);
ResponseEntity<String> anotherResponse =
restTemplate
.getForEntity(
"{baseurl}/configfiles/json/{appId}/{clusterName}/{namespace}?ip={clientIp}",
String.class,
getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, nonGrayClientIp);
Map<String, String> configs = gson.fromJson(response.getBody(), mapResponseType);
Map<String, String> anotherConfigs = gson.fromJson(anotherResponse.getBody(), mapResponseType);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(HttpStatus.OK, anotherResponse.getStatusCode());
assertEquals("override-v1", configs.get("k1"));
assertEquals("gray-v2", configs.get("k2"));
assertEquals("override-v1", anotherConfigs.get("k1"));
assertEquals("default-v2", anotherConfigs.get("k2"));
}
@Test @Test
@Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
...@@ -166,4 +252,7 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration ...@@ -166,4 +252,7 @@ public class ConfigFileControllerIntegrationTest extends AbstractBaseIntegration
assertTrue(result.contains("k2=v2-changed")); assertTrue(result.contains("k2=v2-changed"));
} }
private String assembleKey(String appId, String cluster, String namespace) {
return Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join(appId, cluster, namespace);
}
} }
...@@ -13,12 +13,9 @@ import org.springframework.test.util.ReflectionTestUtils; ...@@ -13,12 +13,9 @@ import org.springframework.test.util.ReflectionTestUtils;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.function.ObjDoubleConsumer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
...@@ -60,13 +57,13 @@ public class InstanceConfigAuditUtilTest { ...@@ -60,13 +57,13 @@ public class InstanceConfigAuditUtilTest {
someDataCenter = "someDataCenter"; someDataCenter = "someDataCenter";
someIp = "someIp"; someIp = "someIp";
someConfigAppId = "someConfigAppId"; someConfigAppId = "someConfigAppId";
someConfigClusterName= "someConfigClusterName"; someConfigClusterName = "someConfigClusterName";
someConfigNamespace = "someConfigNamespace"; someConfigNamespace = "someConfigNamespace";
someReleaseKey = "someReleaseKey"; someReleaseKey = "someReleaseKey";
someAuditModel = new InstanceConfigAuditUtil.InstanceConfigAuditModel(someAppId, someAuditModel = new InstanceConfigAuditUtil.InstanceConfigAuditModel(someAppId,
someClusterName, someDataCenter, someIp, someConfigAppId, someConfigClusterName, someConfigNamespace, someClusterName, someDataCenter, someIp, someConfigAppId, someConfigClusterName,
someReleaseKey); someConfigNamespace, someReleaseKey);
} }
@Test @Test
...@@ -93,7 +90,7 @@ public class InstanceConfigAuditUtilTest { ...@@ -93,7 +90,7 @@ public class InstanceConfigAuditUtilTest {
verify(instanceService, times(1)).findInstance(someAppId, someClusterName, someDataCenter, verify(instanceService, times(1)).findInstance(someAppId, someClusterName, someDataCenter,
someIp); someIp);
verify(instanceService, times(1)).createInstance(any(Instance.class)); verify(instanceService, times(1)).createInstance(any(Instance.class));
verify(instanceService, times(1)).findInstanceConfig(someInstanceId, someConfigAppId, someConfigClusterName, verify(instanceService, times(1)).findInstanceConfig(someInstanceId, someConfigAppId,
someConfigNamespace); someConfigNamespace);
verify(instanceService, times(1)).createInstanceConfig(any(InstanceConfig.class)); verify(instanceService, times(1)).createInstanceConfig(any(InstanceConfig.class));
} }
......
...@@ -4,4 +4,6 @@ DELETE FROM AppNamespace; ...@@ -4,4 +4,6 @@ DELETE FROM AppNamespace;
DELETE FROM Cluster; DELETE FROM Cluster;
DELETE FROM App; DELETE FROM App;
DELETE FROM ReleaseMessage; DELETE FROM ReleaseMessage;
DELETE FROM GrayReleaseRule;
INSERT INTO GrayReleaseRule (`Id`, `AppId`, `ClusterName`, `NamespaceName`, `BranchName`, `Rules`, `ReleaseId`, `BranchStatus`)
VALUES
(1, 'someAppId', 'default', 'application', 'gray-branch-1', '[{"clientAppId":"someAppId","clientIpList":["1.1.1.1"]}]', 986, 1);
INSERT INTO GrayReleaseRule (`Id`, `AppId`, `ClusterName`, `NamespaceName`, `BranchName`, `Rules`, `ReleaseId`, `BranchStatus`)
VALUES
(2, 'somePublicAppId', 'default', 'somePublicNamespace', 'gray-branch-2', '[{"clientAppId":"someAppId","clientIpList":["1.1.1.1"]}]', 985, 1);
...@@ -37,3 +37,7 @@ INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, Namespac ...@@ -37,3 +37,7 @@ INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, Namespac
VALUES (988, 'TEST-RELEASE-KEY6', 'INTEGRATION-TEST-PRIVATE-CONFIG-FILE','First Release','someAppId', 'default', 'anotherNamespace', '{"k1":"v1-file"}'); VALUES (988, 'TEST-RELEASE-KEY6', 'INTEGRATION-TEST-PRIVATE-CONFIG-FILE','First Release','someAppId', 'default', 'anotherNamespace', '{"k1":"v1-file"}');
INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (987, 'TEST-RELEASE-KEY7', 'INTEGRATION-TEST-PUBLIC-CONFIG-FILE','First Release','somePublicAppId', 'default', 'anotherNamespace', '{"k2":"v2-file"}'); VALUES (987, 'TEST-RELEASE-KEY7', 'INTEGRATION-TEST-PUBLIC-CONFIG-FILE','First Release','somePublicAppId', 'default', 'anotherNamespace', '{"k2":"v2-file"}');
INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (986, 'TEST-GRAY-RELEASE-KEY1', 'INTEGRATION-TEST-DEFAULT','Gray Release','someAppId', 'gray-branch-1', 'application', '{"k1":"v1-gray"}');
INSERT INTO RELEASE (id, ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations)
VALUES (985, 'TEST-GRAY-RELEASE-KEY2', 'INTEGRATION-TEST-NAMESPACE','Gray Release','somePublicAppId', 'gray-branch-2', 'somePublicNamespace', '{"k1":"gray-v1", "k2":"gray-v2"}');
\ No newline at end of file
...@@ -8,7 +8,7 @@ import com.ctrip.framework.apollo.core.enums.Env; ...@@ -8,7 +8,7 @@ import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.openapi.dto.OpenReleaseDTO; import com.ctrip.framework.apollo.openapi.dto.OpenReleaseDTO;
import com.ctrip.framework.apollo.openapi.util.OpenApiBeanUtils; import com.ctrip.framework.apollo.openapi.util.OpenApiBeanUtils;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceReleaseModel; import com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel;
import com.ctrip.framework.apollo.portal.service.ReleaseService; import com.ctrip.framework.apollo.portal.service.ReleaseService;
import com.ctrip.framework.apollo.portal.service.UserService; import com.ctrip.framework.apollo.portal.service.UserService;
...@@ -55,7 +55,7 @@ public class ReleaseController { ...@@ -55,7 +55,7 @@ public class ReleaseController {
model.setClusterName(clusterName); model.setClusterName(clusterName);
model.setNamespaceName(namespaceName); model.setNamespaceName(namespaceName);
return OpenApiBeanUtils.transformFromReleaseDTO(releaseService.createRelease(model)); return OpenApiBeanUtils.transformFromReleaseDTO(releaseService.publish(model));
} }
@RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/latest", method = RequestMethod.GET) @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/latest", method = RequestMethod.GET)
......
...@@ -2,11 +2,13 @@ package com.ctrip.framework.apollo.portal.api; ...@@ -2,11 +2,13 @@ package com.ctrip.framework.apollo.portal.api;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.common.dto.AppDTO; import com.ctrip.framework.apollo.common.dto.AppDTO;
import com.ctrip.framework.apollo.common.dto.AppNamespaceDTO; import com.ctrip.framework.apollo.common.dto.AppNamespaceDTO;
import com.ctrip.framework.apollo.common.dto.ClusterDTO; import com.ctrip.framework.apollo.common.dto.ClusterDTO;
import com.ctrip.framework.apollo.common.dto.CommitDTO; import com.ctrip.framework.apollo.common.dto.CommitDTO;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleDTO;
import com.ctrip.framework.apollo.common.dto.InstanceDTO; import com.ctrip.framework.apollo.common.dto.InstanceDTO;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets; import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ItemDTO; import com.ctrip.framework.apollo.common.dto.ItemDTO;
...@@ -14,21 +16,26 @@ import com.ctrip.framework.apollo.common.dto.NamespaceDTO; ...@@ -14,21 +16,26 @@ import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceLockDTO; import com.ctrip.framework.apollo.common.dto.NamespaceLockDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO; import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO; import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseHistoryDTO;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
...@@ -87,7 +94,9 @@ public class AdminServiceAPI { ...@@ -87,7 +94,9 @@ public class AdminServiceAPI {
} }
public void deleteNamespace(Env env, String appId, String clusterName, String namespaceName, String operator) { public void deleteNamespace(Env env, String appId, String clusterName, String namespaceName, String operator) {
restTemplate.delete(env, "apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}?operator={operator}", appId, clusterName, restTemplate
.delete(env, "apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}?operator={operator}", appId,
clusterName,
namespaceName, operator); namespaceName, operator);
} }
...@@ -157,6 +166,7 @@ public class AdminServiceAPI { ...@@ -157,6 +166,7 @@ public class AdminServiceAPI {
cluster.getAppId()); cluster.getAppId());
} }
public void delete(Env env, String appId, String clusterName, String operator) { public void delete(Env env, String appId, String clusterName, String operator) {
restTemplate.delete(env, "apps/{appId}/clusters/{clusterName}?operator={operator}", appId, clusterName, operator); restTemplate.delete(env, "apps/{appId}/clusters/{clusterName}?operator={operator}", appId, clusterName, operator);
} }
...@@ -165,10 +175,24 @@ public class AdminServiceAPI { ...@@ -165,10 +175,24 @@ public class AdminServiceAPI {
@Service @Service
public static class ReleaseAPI extends API { public static class ReleaseAPI extends API {
private static final Joiner JOINER = Joiner.on(",");
public ReleaseDTO loadRelease(Env env, long releaseId) { public ReleaseDTO loadRelease(Env env, long releaseId) {
return restTemplate.get(env, "releases/{releaseId}", ReleaseDTO.class, releaseId); return restTemplate.get(env, "releases/{releaseId}", ReleaseDTO.class, releaseId);
} }
public List<ReleaseDTO> findReleaseByIds(Env env, Set<Long> releaseIds) {
if (CollectionUtils.isEmpty(releaseIds)) {
return Collections.emptyList();
}
ReleaseDTO[]
releases =
restTemplate.get(env, "/releases?releaseIds={releaseIds}", ReleaseDTO[].class, JOINER.join(releaseIds));
return Arrays.asList(releases);
}
public List<ReleaseDTO> findAllReleases(String appId, Env env, String clusterName, String namespaceName, int page, public List<ReleaseDTO> findAllReleases(String appId, Env env, String clusterName, String namespaceName, int page,
int size) { int size) {
ReleaseDTO[] releaseDTOs = restTemplate.get( ReleaseDTO[] releaseDTOs = restTemplate.get(
...@@ -197,12 +221,12 @@ public class AdminServiceAPI { ...@@ -197,12 +221,12 @@ public class AdminServiceAPI {
} }
public ReleaseDTO createRelease(String appId, Env env, String clusterName, String namespace, public ReleaseDTO createRelease(String appId, Env env, String clusterName, String namespace,
String releaseTitle, String comment, String operator) { String releaseName, String releaseComment, String operator) {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); headers.setContentType(MediaType.parseMediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add("name", releaseTitle); parameters.add("name", releaseName);
parameters.add("comment", comment); parameters.add("comment", releaseComment);
parameters.add("operator", operator); parameters.add("operator", operator);
HttpEntity<MultiValueMap<String, String>> entity = HttpEntity<MultiValueMap<String, String>> entity =
new HttpEntity<>(parameters, headers); new HttpEntity<>(parameters, headers);
...@@ -213,6 +237,18 @@ public class AdminServiceAPI { ...@@ -213,6 +237,18 @@ public class AdminServiceAPI {
return response; return response;
} }
public ReleaseDTO updateAndPublish(String appId, Env env, String clusterName, String namespace,
String releaseName, String releaseComment, String branchName,
boolean deleteBranch, ItemChangeSets changeSets) {
return restTemplate.post(env,
"apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/updateAndPublish?"
+ "releaseName={releaseName}&releaseComment={releaseComment}&branchName={branchName}&deleteBranch={deleteBranch}",
changeSets, ReleaseDTO.class,
appId, clusterName, namespace, releaseName, releaseComment, branchName, deleteBranch);
}
public void rollback(Env env, long releaseId, String operator) { public void rollback(Env env, long releaseId, String operator) {
restTemplate.put(env, restTemplate.put(env,
"releases/{releaseId}/rollback?operator={operator}", "releases/{releaseId}/rollback?operator={operator}",
...@@ -247,37 +283,116 @@ public class AdminServiceAPI { ...@@ -247,37 +283,116 @@ public class AdminServiceAPI {
@Service @Service
public static class InstanceAPI extends API { public static class InstanceAPI extends API {
private Joiner joiner = Joiner.on(",");
private ParameterizedTypeReference<PageDTO<InstanceDTO>> pageInstanceDtoType = new ParameterizedTypeReference<PageDTO<InstanceDTO>>() {};
public PageDTO<InstanceDTO> getByRelease(Env env, long releaseId, int page, int size){ private Joiner joiner = Joiner.on(",");
ResponseEntity<PageDTO<InstanceDTO>> entity = restTemplate.get(env, "/instances/by-release?releaseId={releaseId}&page={page}&size={size}", pageInstanceDtoType, releaseId, page, size); private ParameterizedTypeReference<PageDTO<InstanceDTO>>
pageInstanceDtoType =
new ParameterizedTypeReference<PageDTO<InstanceDTO>>() {
};
public PageDTO<InstanceDTO> getByRelease(Env env, long releaseId, int page, int size) {
ResponseEntity<PageDTO<InstanceDTO>>
entity =
restTemplate
.get(env, "/instances/by-release?releaseId={releaseId}&page={page}&size={size}", pageInstanceDtoType,
releaseId, page, size);
return entity.getBody(); return entity.getBody();
} }
public List<InstanceDTO> getByReleasesNotIn(String appId, Env env, String clusterName, String namespaceName, Set<Long> releaseIds){ public List<InstanceDTO> getByReleasesNotIn(String appId, Env env, String clusterName, String namespaceName,
Set<Long> releaseIds) {
InstanceDTO[] instanceDTOs = restTemplate.get(env, "/instances/by-namespace-and-releases-not-in?appId={appId}&clusterName={clusterName}&namespaceName={namespaceName}&releaseIds={releaseIds}", InstanceDTO[]
instanceDTOs =
restTemplate.get(env,
"/instances/by-namespace-and-releases-not-in?appId={appId}&clusterName={clusterName}&namespaceName={namespaceName}&releaseIds={releaseIds}",
InstanceDTO[].class, appId, clusterName, namespaceName, joiner.join(releaseIds)); InstanceDTO[].class, appId, clusterName, namespaceName, joiner.join(releaseIds));
return Arrays.asList(instanceDTOs); return Arrays.asList(instanceDTOs);
} }
public PageDTO<InstanceDTO> getByNamespace(String appId, Env env, String clusterName, String namespaceName, int page, int size){ public PageDTO<InstanceDTO> getByNamespace(String appId, Env env, String clusterName, String namespaceName,
ResponseEntity<PageDTO<InstanceDTO>> entity = restTemplate.get(env, "/instances/by-namespace?appId={appId}&clusterName={clusterName}&namespaceName={namespaceName}&page={page}&size={size}", String instanceAppId,
pageInstanceDtoType, appId, clusterName, namespaceName, page, size); int page, int size) {
ResponseEntity<PageDTO<InstanceDTO>>
entity =
restTemplate.get(env,
"/instances/by-namespace?appId={appId}"
+ "&clusterName={clusterName}&namespaceName={namespaceName}&instanceAppId={instanceAppId}"
+ "&page={page}&size={size}",
pageInstanceDtoType, appId, clusterName, namespaceName, instanceAppId, page, size);
return entity.getBody(); return entity.getBody();
} }
public int getInstanceCountByNamespace(String appId, Env env, String clusterName, String namespaceName){ public int getInstanceCountByNamespace(String appId, Env env, String clusterName, String namespaceName) {
Integer count = restTemplate.get(env, "/instances/by-namespace/count?appId={appId}&clusterName={clusterName}&namespaceName={namespaceName}", Integer
count =
restTemplate.get(env,
"/instances/by-namespace/count?appId={appId}&clusterName={clusterName}&namespaceName={namespaceName}",
Integer.class, appId, clusterName, namespaceName); Integer.class, appId, clusterName, namespaceName);
if (count == null){ if (count == null) {
return 0; return 0;
} }
return count; return count;
} }
} }
@Service
public static class NamespaceBranchAPI extends API {
public NamespaceDTO createBranch(String appId, Env env, String clusterName,
String namespaceName, String operator) {
return restTemplate
.post(env, "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches?operator={operator}",
null, NamespaceDTO.class, appId, clusterName, namespaceName, operator);
}
public NamespaceDTO findBranch(String appId, Env env, String clusterName,
String namespaceName) {
return restTemplate.get(env, "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches",
NamespaceDTO.class, appId, clusterName, namespaceName);
}
public GrayReleaseRuleDTO findBranchGrayRules(String appId, Env env, String clusterName,
String namespaceName, String branchName) {
return restTemplate
.get(env, "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules",
GrayReleaseRuleDTO.class, appId, clusterName, namespaceName, branchName);
}
public void updateBranchGrayRules(String appId, Env env, String clusterName,
String namespaceName, String branchName, GrayReleaseRuleDTO rules) {
restTemplate
.put(env, "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules",
rules, appId, clusterName, namespaceName, branchName);
}
public void deleteBranch(String appId, Env env, String clusterName,
String namespaceName, String branchName, String operator) {
restTemplate.delete(env,
"/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}?operator={operator}",
appId, clusterName, namespaceName, branchName, operator);
}
}
@Service
public static class ReleaseHistoryAPI extends API {
private ParameterizedTypeReference<PageDTO<ReleaseHistoryDTO>> type =
new ParameterizedTypeReference<PageDTO<ReleaseHistoryDTO>>() {};
public PageDTO<ReleaseHistoryDTO> findReleaseHistoriesByNamespace(String appId, Env env, String clusterName,
String namespaceName, int page, int size) {
return restTemplate.get(env,
"/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/histories?page={page}&size={size}",
type, appId, clusterName, namespaceName, page, size).getBody();
}
}
} }
...@@ -139,7 +139,14 @@ public class RetryableRestTemplate { ...@@ -139,7 +139,14 @@ public class RetryableRestTemplate {
} catch (Throwable t) { } catch (Throwable t) {
logger.error("Http request failed, uri: {}, method: {}", uri, HttpMethod.GET, t); logger.error("Http request failed, uri: {}, method: {}", uri, HttpMethod.GET, t);
Cat.logError(t); Cat.logError(t);
if (canRetry(t, HttpMethod.GET)){
Cat.logEvent(CatEventType.API_RETRY, uri); Cat.logEvent(CatEventType.API_RETRY, uri);
}else {// biz exception rethrow
ct.setStatus(t);
ct.complete();
throw t;
}
} }
} }
......
package com.ctrip.framework.apollo.portal.components;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@Component
public class ItemsComparator {
public ItemChangeSets compareIgnoreBlankAndCommentItem(long baseNamespaceId, List<ItemDTO> baseItems, List<ItemDTO> targetItems){
List<ItemDTO> filteredSourceItems = filterBlankAndCommentItem(baseItems);
List<ItemDTO> filteredTargetItems = filterBlankAndCommentItem(targetItems);
Map<String, ItemDTO> sourceItemMap = BeanUtils.mapByKey("key", filteredSourceItems);
Map<String, ItemDTO> targetItemMap = BeanUtils.mapByKey("key", filteredTargetItems);
ItemChangeSets changeSets = new ItemChangeSets();
for (ItemDTO item: targetItems){
String key = item.getKey();
ItemDTO sourceItem = sourceItemMap.get(key);
if (sourceItem == null){//add
ItemDTO copiedItem = copyItem(item);
copiedItem.setNamespaceId(baseNamespaceId);
changeSets.addCreateItem(copiedItem);
}else if (!Objects.equals(sourceItem.getValue(), item.getValue())){//update
//only value & comment can be update
sourceItem.setValue(item.getValue());
sourceItem.setComment(item.getComment());
changeSets.addUpdateItem(sourceItem);
}
}
for (ItemDTO item: baseItems){
String key = item.getKey();
ItemDTO targetItem = targetItemMap.get(key);
if(targetItem == null){//delete
changeSets.addDeleteItem(item);
}
}
return changeSets;
}
private List<ItemDTO> filterBlankAndCommentItem(List<ItemDTO> items){
List<ItemDTO> result = new LinkedList<>();
if (CollectionUtils.isEmpty(items)){
return result;
}
for (ItemDTO item: items){
if (!StringUtils.isEmpty(item.getKey())){
result.add(item);
}
}
return result;
}
private ItemDTO copyItem(ItemDTO sourceItem){
ItemDTO copiedItem = new ItemDTO();
copiedItem.setKey(sourceItem.getKey());
copiedItem.setValue(sourceItem.getValue());
copiedItem.setComment(sourceItem.getComment());
return copiedItem;
}
}
...@@ -52,9 +52,9 @@ public class AuthConfiguration { ...@@ -52,9 +52,9 @@ public class AuthConfiguration {
@Bean @Bean
public ServletListenerRegistrationBean redisAppSettingListner() { public ServletListenerRegistrationBean redisAppSettingListner() {
ServletListenerRegistrationBean redisAppSettingListner = new ServletListenerRegistrationBean(); ServletListenerRegistrationBean redisAppSettingListener = new ServletListenerRegistrationBean();
redisAppSettingListner.setListener(listener("org.jasig.cas.client.credis.CRedisAppSettingListner")); redisAppSettingListener.setListener(listener("org.jasig.cas.client.credis.CRedisAppSettingListner"));
return redisAppSettingListner; return redisAppSettingListener;
} }
@Bean @Bean
......
...@@ -20,4 +20,11 @@ public interface CatEventType { ...@@ -20,4 +20,11 @@ public interface CatEventType {
String USER_ACCESS = "User.Access"; String USER_ACCESS = "User.Access";
String CREATE_GRAY_RELEASE = "GrayRelease.Create";
String DELETE_GRAY_RELEASE = "GrayRelease.Delete";
String MERGE_GRAY_RELEASE = "GrayRelease.Merge";
String UPDATE_GRAY_RELEASE_RULE = "GrayReleaseRule.Update";
} }
...@@ -60,7 +60,6 @@ public class AppController { ...@@ -60,7 +60,6 @@ public class AppController {
} }
@RequestMapping("/by-owner") @RequestMapping("/by-owner")
public List<App> findAppsByOwner(@RequestParam("owner") String owner, Pageable page){ public List<App> findAppsByOwner(@RequestParam("owner") String owner, Pageable page){
return appService.findByOwnerName(owner, page); return appService.findByOwnerName(owner, page);
......
...@@ -32,7 +32,8 @@ public class InstanceController { ...@@ -32,7 +32,8 @@ public class InstanceController {
@RequestMapping("/envs/{env}/instances/by-release") @RequestMapping("/envs/{env}/instances/by-release")
public PageDTO<InstanceDTO> getByRelease(@PathVariable String env, @RequestParam long releaseId, public PageDTO<InstanceDTO> getByRelease(@PathVariable String env, @RequestParam long releaseId,
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { @RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return instanceService.getByRelease(Env.valueOf(env), releaseId, page, size); return instanceService.getByRelease(Env.valueOf(env), releaseId, page, size);
} }
...@@ -40,14 +41,17 @@ public class InstanceController { ...@@ -40,14 +41,17 @@ public class InstanceController {
@RequestMapping("/envs/{env}/instances/by-namespace") @RequestMapping("/envs/{env}/instances/by-namespace")
public PageDTO<InstanceDTO> getByNamespace(@PathVariable String env, @RequestParam String appId, public PageDTO<InstanceDTO> getByNamespace(@PathVariable String env, @RequestParam String appId,
@RequestParam String clusterName, @RequestParam String namespaceName, @RequestParam String clusterName, @RequestParam String namespaceName,
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size) { @RequestParam(required = false) String instanceAppId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return instanceService.getByNamespace(Env.valueOf(env), appId, clusterName, namespaceName, page, size); return instanceService.getByNamespace(Env.valueOf(env), appId, clusterName, namespaceName, instanceAppId, page, size);
} }
@RequestMapping("/envs/{env}/instances/by-namespace/count") @RequestMapping("/envs/{env}/instances/by-namespace/count")
public ResponseEntity<Number> getInstanceCountByNamespace(@PathVariable String env, @RequestParam String appId, public ResponseEntity<Number> getInstanceCountByNamespace(@PathVariable String env, @RequestParam String appId,
@RequestParam String clusterName, @RequestParam String namespaceName) { @RequestParam String clusterName,
@RequestParam String namespaceName) {
int count = instanceService.getInstanceCountByNamepsace(appId, Env.valueOf(env), clusterName, namespaceName); int count = instanceService.getInstanceCountByNamepsace(appId, Env.valueOf(env), clusterName, namespaceName);
return ResponseEntity.ok(new Number(count)); return ResponseEntity.ok(new Number(count));
...@@ -61,7 +65,7 @@ public class InstanceController { ...@@ -61,7 +65,7 @@ public class InstanceController {
Set<Long> releaseIdSet = RELEASES_SPLITTER.splitToList(releaseIds).stream().map(Long::parseLong) Set<Long> releaseIdSet = RELEASES_SPLITTER.splitToList(releaseIds).stream().map(Long::parseLong)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
if (CollectionUtils.isEmpty(releaseIdSet)){ if (CollectionUtils.isEmpty(releaseIdSet)) {
throw new BadRequestException("release ids can not be empty"); throw new BadRequestException("release ids can not be empty");
} }
......
...@@ -5,8 +5,8 @@ import com.ctrip.framework.apollo.common.exception.BadRequestException; ...@@ -5,8 +5,8 @@ import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.auth.UserInfoHolder; import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceSyncModel; import com.ctrip.framework.apollo.portal.entity.model.NamespaceSyncModel;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceTextModel; import com.ctrip.framework.apollo.portal.entity.model.NamespaceTextModel;
import com.ctrip.framework.apollo.portal.entity.vo.ItemDiffs; import com.ctrip.framework.apollo.portal.entity.vo.ItemDiffs;
import com.ctrip.framework.apollo.portal.service.ItemService; import com.ctrip.framework.apollo.portal.service.ItemService;
...@@ -107,6 +107,15 @@ public class ItemController { ...@@ -107,6 +107,15 @@ public class ItemController {
return items; return items;
} }
@RequestMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/items")
public List<ItemDTO> findBranchItems(@PathVariable("appId") String appId, @PathVariable String env,
@PathVariable("clusterName") String clusterName,
@PathVariable("namespaceName") String namespaceName,
@PathVariable("branchName") String branchName) {
return findItems(appId, env, branchName, namespaceName, "lastModifiedTime");
}
@RequestMapping(value = "/namespaces/{namespaceName}/diff", method = RequestMethod.POST, consumes = { @RequestMapping(value = "/namespaces/{namespaceName}/diff", method = RequestMethod.POST, consumes = {
"application/json"}) "application/json"})
public List<ItemDiffs> diff(@RequestBody NamespaceSyncModel model) { public List<ItemDiffs> diff(@RequestBody NamespaceSyncModel model) {
......
package com.ctrip.framework.apollo.portal.controller;
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.dto.ReleaseDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceVO;
import com.ctrip.framework.apollo.portal.service.NamespaceBranchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NamespaceBranchController {
@Autowired
private NamespaceBranchService namespaceBranchService;
@RequestMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches")
public NamespaceVO findBranch(@PathVariable String appId,
@PathVariable String env,
@PathVariable String clusterName,
@PathVariable String namespaceName) {
return namespaceBranchService.findBranch(appId, Env.valueOf(env), clusterName, namespaceName);
}
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches", method = RequestMethod.POST)
public NamespaceDTO createBranch(@PathVariable String appId,
@PathVariable String env,
@PathVariable String clusterName,
@PathVariable String namespaceName) {
return namespaceBranchService.createBranch(appId, Env.valueOf(env), clusterName, namespaceName);
}
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}", method = RequestMethod.DELETE)
public void deleteBranch(@PathVariable String appId,
@PathVariable String env,
@PathVariable String clusterName,
@PathVariable String namespaceName,
@PathVariable String branchName) {
namespaceBranchService.deleteBranch(appId, Env.valueOf(env), clusterName, namespaceName, branchName);
}
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/merge", method = RequestMethod.POST)
public ReleaseDTO merge(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName,
@PathVariable String branchName, @RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,
@RequestBody NamespaceReleaseModel model) {
ReleaseDTO createdRelease = namespaceBranchService.merge(appId, Env.valueOf(env), clusterName, namespaceName, branchName,
model.getReleaseTitle(), model.getReleaseComment(), deleteBranch);
return createdRelease;
}
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules", method = RequestMethod.GET)
public GrayReleaseRuleDTO getBranchGrayRules(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName,
@PathVariable String namespaceName,
@PathVariable String branchName) {
return namespaceBranchService.findBranchGrayRules(appId, Env.valueOf(env), clusterName, namespaceName, branchName);
}
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules", method = RequestMethod.PUT)
public void updateBranchRules(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName,
@PathVariable String branchName, @RequestBody GrayReleaseRuleDTO rules) {
namespaceBranchService
.updateBranchGrayRules(appId, Env.valueOf(env), clusterName, namespaceName, branchName, rules);
}
}
...@@ -12,8 +12,8 @@ import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; ...@@ -12,8 +12,8 @@ import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.auth.UserInfoHolder; import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
import com.ctrip.framework.apollo.portal.entity.model.NamespaceCreationModel;
import com.ctrip.framework.apollo.portal.constant.RoleType; import com.ctrip.framework.apollo.portal.constant.RoleType;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceCreationModel;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceVO; import com.ctrip.framework.apollo.portal.entity.vo.NamespaceVO;
import com.ctrip.framework.apollo.portal.listener.AppNamespaceCreationEvent; import com.ctrip.framework.apollo.portal.listener.AppNamespaceCreationEvent;
import com.ctrip.framework.apollo.portal.service.AppNamespaceService; import com.ctrip.framework.apollo.portal.service.AppNamespaceService;
...@@ -66,6 +66,20 @@ public class NamespaceController { ...@@ -66,6 +66,20 @@ public class NamespaceController {
return appNamespaceService.findPublicAppNamespaces(); return appNamespaceService.findPublicAppNamespaces();
} }
@RequestMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces")
public List<NamespaceVO> findNamespaces(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName) {
return namespaceService.findNamespaces(appId, Env.valueOf(env), clusterName);
}
@RequestMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName:.+}")
public NamespaceVO findNamespaces(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName) {
return namespaceService.loadNamespace(appId, Env.valueOf(env), clusterName, namespaceName);
}
@PreAuthorize(value = "@permissionValidator.hasCreateNamespacePermission(#appId)") @PreAuthorize(value = "@permissionValidator.hasCreateNamespacePermission(#appId)")
@RequestMapping(value = "/apps/{appId}/namespaces", method = RequestMethod.POST) @RequestMapping(value = "/apps/{appId}/namespaces", method = RequestMethod.POST)
public ResponseEntity<Void> createNamespace(@PathVariable String appId, public ResponseEntity<Void> createNamespace(@PathVariable String appId,
...@@ -79,7 +93,8 @@ public class NamespaceController { ...@@ -79,7 +93,8 @@ public class NamespaceController {
NamespaceDTO namespace = model.getNamespace(); NamespaceDTO namespace = model.getNamespace();
namespaceName = namespace.getNamespaceName(); namespaceName = namespace.getNamespaceName();
RequestPrecondition RequestPrecondition
.checkArgumentsNotEmpty(model.getEnv(), namespace.getAppId(), namespace.getClusterName(), namespace.getNamespaceName()); .checkArgumentsNotEmpty(model.getEnv(), namespace.getAppId(), namespace.getClusterName(),
namespace.getNamespaceName());
try { try {
// TODO: 16/6/17 某些环境创建失败,统一处理这种场景 // TODO: 16/6/17 某些环境创建失败,统一处理这种场景
...@@ -87,15 +102,18 @@ public class NamespaceController { ...@@ -87,15 +102,18 @@ public class NamespaceController {
} catch (Exception e) { } catch (Exception e) {
logger.error("create namespace fail.", e); logger.error("create namespace fail.", e);
Cat.logError( Cat.logError(
String.format("create namespace fail. (env=%s namespace=%s)", model.getEnv(), namespace.getNamespaceName()), e); String.format("create namespace fail. (env=%s namespace=%s)", model.getEnv(),
namespace.getNamespaceName()), e);
} }
} }
//default assign modify、release namespace role to namespace creator //default assign modify、release namespace role to namespace creator
String loginUser = userInfoHolder.getUser().getUserId(); String loginUser = userInfoHolder.getUser().getUserId();
rolePermissionService.assignRoleToUsers(RoleUtils.buildNamespaceRoleName(appId, namespaceName, RoleType.MODIFY_NAMESPACE), rolePermissionService
.assignRoleToUsers(RoleUtils.buildNamespaceRoleName(appId, namespaceName, RoleType.MODIFY_NAMESPACE),
Sets.newHashSet(loginUser), loginUser); Sets.newHashSet(loginUser), loginUser);
rolePermissionService.assignRoleToUsers(RoleUtils.buildNamespaceRoleName(appId, namespaceName, RoleType.RELEASE_NAMESPACE), rolePermissionService
.assignRoleToUsers(RoleUtils.buildNamespaceRoleName(appId, namespaceName, RoleType.RELEASE_NAMESPACE),
Sets.newHashSet(loginUser), loginUser); Sets.newHashSet(loginUser), loginUser);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
...@@ -104,7 +122,7 @@ public class NamespaceController { ...@@ -104,7 +122,7 @@ public class NamespaceController {
@PreAuthorize(value = "@permissionValidator.isSuperAdmin()") @PreAuthorize(value = "@permissionValidator.isSuperAdmin()")
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName:.+}", method = RequestMethod.DELETE) @RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName:.+}", method = RequestMethod.DELETE)
public ResponseEntity<Void> deleteNamespace(@PathVariable String appId, @PathVariable String env, public ResponseEntity<Void> deleteNamespace(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName){ @PathVariable String clusterName, @PathVariable String namespaceName) {
namespaceService.deleteNamespace(appId, Env.valueOf(env), clusterName, namespaceName); namespaceService.deleteNamespace(appId, Env.valueOf(env), clusterName, namespaceName);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
...@@ -142,11 +160,4 @@ public class NamespaceController { ...@@ -142,11 +160,4 @@ public class NamespaceController {
return createdAppNamespace; return createdAppNamespace;
} }
@RequestMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces")
public List<NamespaceVO> findNamespaces(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName) {
return namespaceService.findNamespaces(appId, Env.valueOf(env), clusterName);
}
} }
...@@ -3,7 +3,7 @@ package com.ctrip.framework.apollo.portal.controller; ...@@ -3,7 +3,7 @@ package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO; import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.utils.RequestPrecondition; import com.ctrip.framework.apollo.common.utils.RequestPrecondition;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceReleaseModel; import com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel;
import com.ctrip.framework.apollo.portal.entity.vo.ReleaseCompareResult; import com.ctrip.framework.apollo.portal.entity.vo.ReleaseCompareResult;
import com.ctrip.framework.apollo.portal.entity.vo.ReleaseVO; import com.ctrip.framework.apollo.portal.entity.vo.ReleaseVO;
import com.ctrip.framework.apollo.portal.service.ReleaseService; import com.ctrip.framework.apollo.portal.service.ReleaseService;
...@@ -39,7 +39,7 @@ public class ReleaseController { ...@@ -39,7 +39,7 @@ public class ReleaseController {
model.setClusterName(clusterName); model.setClusterName(clusterName);
model.setNamespaceName(namespaceName); model.setNamespaceName(namespaceName);
return releaseService.createRelease(model); return releaseService.publish(model);
} }
...@@ -73,10 +73,10 @@ public class ReleaseController { ...@@ -73,10 +73,10 @@ public class ReleaseController {
@RequestMapping(value = "/envs/{env}/releases/compare") @RequestMapping(value = "/envs/{env}/releases/compare")
public ReleaseCompareResult compareRelease(@PathVariable String env, public ReleaseCompareResult compareRelease(@PathVariable String env,
@RequestParam long firstReleaseId, @RequestParam long baseReleaseId,
@RequestParam long secondReleaseId) { @RequestParam long toCompareReleaseId) {
return releaseService.compare(Env.valueOf(env), firstReleaseId, secondReleaseId); return releaseService.compare(Env.valueOf(env), baseReleaseId, toCompareReleaseId);
} }
......
package com.ctrip.framework.apollo.portal.controller;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.entity.vo.ReleaseHistoryVO;
import com.ctrip.framework.apollo.portal.service.ReleaseHistoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class ReleaseHistoryController {
@Autowired
private ReleaseHistoryService releaseHistoryService;
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/releases/histories",
method = RequestMethod.GET)
public List<ReleaseHistoryVO> findReleaseHistoriesByNamespace(@PathVariable String appId,
@PathVariable String env,
@PathVariable String clusterName,
@PathVariable String namespaceName,
@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = "10") int size) {
return releaseHistoryService.findNamespaceReleaseHistory(appId, Env.valueOf(env), clusterName ,namespaceName, page, size);
}
}
package com.ctrip.framework.apollo.portal.entity.form; package com.ctrip.framework.apollo.portal.entity.model;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO; import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
......
package com.ctrip.framework.apollo.portal.entity.form; package com.ctrip.framework.apollo.portal.entity.model;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
......
package com.ctrip.framework.apollo.portal.entity.form; package com.ctrip.framework.apollo.portal.entity.model;
import com.ctrip.framework.apollo.common.dto.ItemDTO; import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier; import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier;
......
package com.ctrip.framework.apollo.portal.entity.form; package com.ctrip.framework.apollo.portal.entity.model;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
......
package com.ctrip.framework.apollo.portal.entity.form; package com.ctrip.framework.apollo.portal.entity.model;
public interface Verifiable { public interface Verifiable {
......
...@@ -2,7 +2,7 @@ package com.ctrip.framework.apollo.portal.entity.vo; ...@@ -2,7 +2,7 @@ package com.ctrip.framework.apollo.portal.entity.vo;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.entity.form.Verifiable; import com.ctrip.framework.apollo.portal.entity.model.Verifiable;
public class NamespaceIdentifier implements Verifiable { public class NamespaceIdentifier implements Verifiable {
private String appId; private String appId;
......
package com.ctrip.framework.apollo.portal.entity.vo;
import com.ctrip.framework.apollo.common.entity.EntityPair;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class ReleaseHistoryVO {
private long id;
private String appId;
private String clusterName;
private String namespaceName;
private String branchName;
private String operator;
private long releaseId;
private String releaseTitle;
private String releaseComment;
private Date releaseTime;
private String releaseTimeFormatted;
private List<EntityPair<String>> configuration;
private long previousReleaseId;
private int operation;
private Map<String, Object> operationContext;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public String getNamespaceName() {
return namespaceName;
}
public void setNamespaceName(String namespaceName) {
this.namespaceName = namespaceName;
}
public String getBranchName() {
return branchName;
}
public void setBranchName(String branchName) {
this.branchName = branchName;
}
public long getReleaseId() {
return releaseId;
}
public void setReleaseId(long releaseId) {
this.releaseId = releaseId;
}
public long getPreviousReleaseId() {
return previousReleaseId;
}
public void setPreviousReleaseId(long previousReleaseId) {
this.previousReleaseId = previousReleaseId;
}
public int getOperation() {
return operation;
}
public void setOperation(int operation) {
this.operation = operation;
}
public Map<String, Object> getOperationContext() {
return operationContext;
}
public void setOperationContext(Map<String, Object> operationContext) {
this.operationContext = operationContext;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public String getReleaseTitle() {
return releaseTitle;
}
public void setReleaseTitle(String releaseTitle) {
this.releaseTitle = releaseTitle;
}
public String getReleaseComment() {
return releaseComment;
}
public void setReleaseComment(String releaseComment) {
this.releaseComment = releaseComment;
}
public Date getReleaseTime() {
return releaseTime;
}
public void setReleaseTime(Date releaseTime) {
this.releaseTime = releaseTime;
}
public String getReleaseTimeFormatted() {
return releaseTimeFormatted;
}
public void setReleaseTimeFormatted(String releaseTimeFormatted) {
this.releaseTimeFormatted = releaseTimeFormatted;
}
public List<EntityPair<String>> getConfiguration() {
return configuration;
}
public void setConfiguration(
List<EntityPair<String>> configuration) {
this.configuration = configuration;
}
}
...@@ -40,4 +40,8 @@ public class ClusterService { ...@@ -40,4 +40,8 @@ public class ClusterService {
clusterAPI.delete(env, appId, clusterName, userInfoHolder.getUser().getUserId()); clusterAPI.delete(env, appId, clusterName, userInfoHolder.getUser().getUserId());
} }
public ClusterDTO loadCluster(String appId, Env env, String clusterName){
return clusterAPI.loadCluster(appId, env, clusterName);
}
} }
...@@ -22,8 +22,9 @@ public class InstanceService { ...@@ -22,8 +22,9 @@ public class InstanceService {
return instanceAPI.getByRelease(env, releaseId, page, size); return instanceAPI.getByRelease(env, releaseId, page, size);
} }
public PageDTO<InstanceDTO> getByNamespace(Env env, String appId, String clusterName, String namespaceName, int page, int size){ public PageDTO<InstanceDTO> getByNamespace(Env env, String appId, String clusterName, String namespaceName,
return instanceAPI.getByNamespace(appId, env, clusterName, namespaceName, page, size); String instanceAppId, int page, int size){
return instanceAPI.getByNamespace(appId, env, clusterName, namespaceName, instanceAppId, page, size);
} }
public int getInstanceCountByNamepsace(String appId, Env env, String clusterName, String namespaceName){ public int getInstanceCountByNamepsace(String appId, Env env, String clusterName, String namespaceName){
......
...@@ -12,7 +12,7 @@ import com.ctrip.framework.apollo.core.utils.StringUtils; ...@@ -12,7 +12,7 @@ import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.auth.UserInfoHolder; import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
import com.ctrip.framework.apollo.portal.constant.CatEventType; import com.ctrip.framework.apollo.portal.constant.CatEventType;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceTextModel; import com.ctrip.framework.apollo.portal.entity.model.NamespaceTextModel;
import com.ctrip.framework.apollo.portal.entity.vo.ItemDiffs; import com.ctrip.framework.apollo.portal.entity.vo.ItemDiffs;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier; import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier;
import com.ctrip.framework.apollo.portal.service.txtresolver.ConfigTextResolver; import com.ctrip.framework.apollo.portal.service.txtresolver.ConfigTextResolver;
...@@ -71,13 +71,17 @@ public class ItemService { ...@@ -71,13 +71,17 @@ public class ItemService {
} }
changeSets.setDataChangeLastModifiedBy(userInfoHolder.getUser().getUserId()); changeSets.setDataChangeLastModifiedBy(userInfoHolder.getUser().getUserId());
itemAPI.updateItemsByChangeSet(appId, env, clusterName, namespaceName, changeSets); updateItems(appId, env, clusterName, namespaceName, changeSets);
Cat.logEvent(CatEventType.MODIFY_NAMESPACE_BY_TEXT, Cat.logEvent(CatEventType.MODIFY_NAMESPACE_BY_TEXT,
String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName)); String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName));
Cat.logEvent(CatEventType.MODIFY_NAMESPACE, String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName)); Cat.logEvent(CatEventType.MODIFY_NAMESPACE, String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName));
} }
public void updateItems(String appId, Env env, String clusterName, String namespaceName, ItemChangeSets changeSets){
itemAPI.updateItemsByChangeSet(appId, env, clusterName, namespaceName, changeSets);
}
public ItemDTO createItem(String appId, Env env, String clusterName, String namespaceName, ItemDTO item) { public ItemDTO createItem(String appId, Env env, String clusterName, String namespaceName, ItemDTO item) {
NamespaceDTO namespace = namespaceAPI.loadNamespace(appId, env, clusterName, namespaceName); NamespaceDTO namespace = namespaceAPI.loadNamespace(appId, env, clusterName, namespaceName);
......
package com.ctrip.framework.apollo.portal.service;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleDTO;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.auth.PermissionValidator;
import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
import com.ctrip.framework.apollo.portal.components.ItemsComparator;
import com.ctrip.framework.apollo.portal.constant.CatEventType;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceVO;
import com.dianping.cat.Cat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
@Service
public class NamespaceBranchService {
@Autowired
private ItemsComparator itemsComparator;
@Autowired
private UserInfoHolder userInfoHolder;
@Autowired
private NamespaceService namespaceService;
@Autowired
private ItemService itemService;
@Autowired
private AdminServiceAPI.NamespaceBranchAPI namespaceBranchAPI;
@Autowired
private ReleaseService releaseService;
@Autowired
private PermissionValidator permissionValidator;
@Transactional
public NamespaceDTO createBranch(String appId, Env env, String parentClusterName, String namespaceName) {
NamespaceDTO createdBranch = namespaceBranchAPI.createBranch(appId, env, parentClusterName, namespaceName,
userInfoHolder.getUser().getUserId());
Cat.logEvent(CatEventType.CREATE_GRAY_RELEASE, String.format("%s+%s+%s+%s", appId, env, parentClusterName,
namespaceName));
return createdBranch;
}
public GrayReleaseRuleDTO findBranchGrayRules(String appId, Env env, String clusterName,
String namespaceName, String branchName) {
return namespaceBranchAPI.findBranchGrayRules(appId, env, clusterName, namespaceName, branchName);
}
public void updateBranchGrayRules(String appId, Env env, String clusterName, String namespaceName,
String branchName, GrayReleaseRuleDTO rules) {
String operator = userInfoHolder.getUser().getUserId();
rules.setDataChangeCreatedBy(operator);
rules.setDataChangeLastModifiedBy(operator);
namespaceBranchAPI.updateBranchGrayRules(appId, env, clusterName, namespaceName, branchName, rules);
Cat.logEvent(CatEventType.UPDATE_GRAY_RELEASE_RULE,
String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName),
"success", String.valueOf(rules.getRuleItems()));
}
public void deleteBranch(String appId, Env env, String clusterName, String namespaceName,
String branchName) {
String operator = userInfoHolder.getUser().getUserId();
//Refusing request if user has not release permission and branch has been released
if (!permissionValidator.hasReleaseNamespacePermission(appId, namespaceName)
&& (!permissionValidator.hasModifyNamespacePermission(appId, namespaceName) ||
releaseService.loadLatestRelease(appId, env, branchName, namespaceName) != null)) {
throw new BadRequestException("Forbidden operation. "
+ "Cause by: you has not release permission "
+ "or you has not modify permission "
+ "or you has modify permission but branch has been released");
}
namespaceBranchAPI.deleteBranch(appId, env, clusterName, namespaceName, branchName, operator);
Cat.logEvent(CatEventType.DELETE_GRAY_RELEASE,
String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName));
}
public ReleaseDTO merge(String appId, Env env, String clusterName, String namespaceName,
String branchName, String title, String comment, boolean deleteBranch) {
ItemChangeSets changeSets = calculateBranchChangeSet(appId, env, clusterName, namespaceName, branchName);
ReleaseDTO
mergedResult =
releaseService.updateAndPublish(appId, env, clusterName, namespaceName, title, comment, branchName, deleteBranch, changeSets);
Cat.logEvent(CatEventType.MERGE_GRAY_RELEASE,
String.format("%s+%s+%s+%s", appId, env, clusterName, namespaceName));
return mergedResult;
}
private ItemChangeSets calculateBranchChangeSet(String appId, Env env, String clusterName, String namespaceName,
String branchName) {
NamespaceVO parentNamespace = namespaceService.loadNamespace(appId, env, clusterName, namespaceName);
if (parentNamespace == null) {
throw new BadRequestException("base namespace not existed");
}
if (parentNamespace.getItemModifiedCnt() > 0) {
throw new BadRequestException("Merge operation failed. Because master has modified items");
}
List<ItemDTO> masterItems = itemService.findItems(appId, env, clusterName, namespaceName);
List<ItemDTO> branchItems = itemService.findItems(appId, env, branchName, namespaceName);
ItemChangeSets changeSets = itemsComparator.compareIgnoreBlankAndCommentItem(parentNamespace.getBaseInfo().getId(),
masterItems, branchItems);
changeSets.setDeleteItems(Collections.emptyList());
changeSets.setDataChangeLastModifiedBy(userInfoHolder.getUser().getUserId());
return changeSets;
}
public NamespaceDTO findBranchBaseInfo(String appId, Env env, String clusterName, String namespaceName) {
return namespaceBranchAPI.findBranch(appId, env, clusterName, namespaceName);
}
public NamespaceVO findBranch(String appId, Env env, String clusterName, String namespaceName) {
NamespaceDTO namespaceDTO = findBranchBaseInfo(appId, env, clusterName, namespaceName);
if (namespaceDTO == null) {
return null;
}
return namespaceService.loadNamespace(appId, env, namespaceDTO.getClusterName(), namespaceName);
}
}
package com.ctrip.framework.apollo.portal.service;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.common.constants.GsonType;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseHistoryDTO;
import com.ctrip.framework.apollo.common.entity.EntityPair;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.entity.vo.ReleaseHistoryVO;
import com.ctrip.framework.apollo.portal.util.RelativeDateFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Service
public class ReleaseHistoryService {
private Gson gson = new Gson();
@Autowired
private AdminServiceAPI.ReleaseHistoryAPI releaseHistoryAPI;
@Autowired
private ReleaseService releaseService;
public List<ReleaseHistoryVO> findNamespaceReleaseHistory(String appId, Env env, String clusterName,
String namespaceName, int page, int size) {
PageDTO<ReleaseHistoryDTO> result = releaseHistoryAPI.findReleaseHistoriesByNamespace(appId, env, clusterName,
namespaceName, page, size);
if (result == null || !result.hasContent()) {
return Collections.emptyList();
}
List<ReleaseHistoryDTO> content = result.getContent();
Set<Long> releaseIds = new HashSet<>();
for (ReleaseHistoryDTO releaseHistoryDTO : content) {
long releaseId = releaseHistoryDTO.getReleaseId();
if (releaseId != 0) {
releaseIds.add(releaseId);
}
}
List<ReleaseDTO> releases = releaseService.findReleaseByIds(env, releaseIds);
return convertReleaseHistoryDTO2VO(content, releases);
}
private List<ReleaseHistoryVO> convertReleaseHistoryDTO2VO(List<ReleaseHistoryDTO> source,
List<ReleaseDTO> releases) {
Map<Long, ReleaseDTO> releasesMap = BeanUtils.mapByKey("id", releases);
List<ReleaseHistoryVO> vos = new ArrayList<>(source.size());
for (ReleaseHistoryDTO dto : source) {
ReleaseHistoryVO vo = new ReleaseHistoryVO();
vo.setId(dto.getId());
vo.setAppId(dto.getAppId());
vo.setClusterName(dto.getClusterName());
vo.setNamespaceName(dto.getNamespaceName());
vo.setBranchName(dto.getBranchName());
vo.setReleaseId(dto.getReleaseId());
vo.setPreviousReleaseId(dto.getPreviousReleaseId());
vo.setOperator(dto.getDataChangeCreatedBy());
vo.setOperation(dto.getOperation());
Date releaseTime = dto.getDataChangeLastModifiedTime();
vo.setReleaseTime(releaseTime);
vo.setReleaseTimeFormatted(RelativeDateFormat.format(releaseTime));
vo.setOperationContext(dto.getOperationContext());
//set release info
ReleaseDTO release = releasesMap.get(dto.getReleaseId());
setReleaseInfoToReleaseHistoryVO(vo, release);
vos.add(vo);
}
return vos;
}
private void setReleaseInfoToReleaseHistoryVO(ReleaseHistoryVO vo, ReleaseDTO release) {
if (release != null) {
vo.setReleaseTitle(release.getName());
vo.setReleaseComment(release.getComment());
Map<String, String> configuration = gson.fromJson(release.getConfigurations(), GsonType.CONFIG);
List<EntityPair<String>> items = new ArrayList<>(configuration.size());
for (Map.Entry<String, String> entry : configuration.entrySet()) {
EntityPair<String> entityPair = new EntityPair<>(entry.getKey(), entry.getValue());
items.add(entityPair);
}
vo.setConfiguration(items);
} else {
vo.setReleaseTitle("no release information");
vo.setConfiguration(null);
}
}
}
...@@ -4,13 +4,14 @@ import com.google.common.base.Objects; ...@@ -4,13 +4,14 @@ import com.google.common.base.Objects;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO; import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.auth.UserInfoHolder; import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
import com.ctrip.framework.apollo.portal.constant.CatEventType; import com.ctrip.framework.apollo.portal.constant.CatEventType;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceReleaseModel; import com.ctrip.framework.apollo.portal.entity.model.NamespaceReleaseModel;
import com.ctrip.framework.apollo.portal.entity.vo.KVEntity; import com.ctrip.framework.apollo.portal.entity.vo.KVEntity;
import com.ctrip.framework.apollo.portal.entity.vo.ReleaseCompareResult; import com.ctrip.framework.apollo.portal.entity.vo.ReleaseCompareResult;
import com.ctrip.framework.apollo.portal.entity.vo.ReleaseVO; import com.ctrip.framework.apollo.portal.entity.vo.ReleaseVO;
...@@ -23,6 +24,7 @@ import org.springframework.util.CollectionUtils; ...@@ -23,6 +24,7 @@ import org.springframework.util.CollectionUtils;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
...@@ -41,7 +43,7 @@ public class ReleaseService { ...@@ -41,7 +43,7 @@ public class ReleaseService {
@Autowired @Autowired
private AdminServiceAPI.ReleaseAPI releaseAPI; private AdminServiceAPI.ReleaseAPI releaseAPI;
public ReleaseDTO createRelease(NamespaceReleaseModel model) { public ReleaseDTO publish(NamespaceReleaseModel model) {
String appId = model.getAppId(); String appId = model.getAppId();
Env env = model.getEnv(); Env env = model.getEnv();
String clusterName = model.getClusterName(); String clusterName = model.getClusterName();
...@@ -57,6 +59,12 @@ public class ReleaseService { ...@@ -57,6 +59,12 @@ public class ReleaseService {
return releaseDTO; return releaseDTO;
} }
public ReleaseDTO updateAndPublish(String appId, Env env, String clusterName, String namespaceName,
String releaseTitle, String releaseComment, String branchName, boolean deleteBranch, ItemChangeSets changeSets){
return releaseAPI.updateAndPublish(appId, env, clusterName, namespaceName, releaseTitle, releaseComment, branchName, deleteBranch, changeSets);
}
public List<ReleaseVO> findAllReleases(String appId, Env env, String clusterName, String namespaceName, int page, public List<ReleaseVO> findAllReleases(String appId, Env env, String clusterName, String namespaceName, int page,
int size) { int size) {
List<ReleaseDTO> releaseDTOs = releaseAPI.findAllReleases(appId, env, clusterName, namespaceName, page, size); List<ReleaseDTO> releaseDTOs = releaseAPI.findAllReleases(appId, env, clusterName, namespaceName, page, size);
...@@ -90,6 +98,10 @@ public class ReleaseService { ...@@ -90,6 +98,10 @@ public class ReleaseService {
return releaseAPI.findActiveReleases(appId, env, clusterName, namespaceName, page, size); return releaseAPI.findActiveReleases(appId, env, clusterName, namespaceName, page, size);
} }
public List<ReleaseDTO> findReleaseByIds(Env env, Set<Long> releaseIds){
return releaseAPI.findReleaseByIds(env, releaseIds);
}
public ReleaseDTO loadLatestRelease(String appId, Env env, String clusterName, String namespaceName) { public ReleaseDTO loadLatestRelease(String appId, Env env, String clusterName, String namespaceName) {
return releaseAPI.loadLatestRelease(appId, env, clusterName, namespaceName); return releaseAPI.loadLatestRelease(appId, env, clusterName, namespaceName);
} }
...@@ -98,24 +110,31 @@ public class ReleaseService { ...@@ -98,24 +110,31 @@ public class ReleaseService {
releaseAPI.rollback(env, releaseId, userInfoHolder.getUser().getUserId()); releaseAPI.rollback(env, releaseId, userInfoHolder.getUser().getUserId());
} }
public ReleaseCompareResult compare(Env env, long firstReleaseId, long secondReleaseId) { public ReleaseCompareResult compare(Env env, long baseReleaseId, long toCompareReleaseId) {
ReleaseDTO firstRelease = releaseAPI.loadRelease(env, firstReleaseId); Map<String, String> baseReleaseConfiguration = new HashMap<>();
ReleaseDTO secondRelease = releaseAPI.loadRelease(env, secondReleaseId); Map<String, String> toCompareReleaseConfiguration = new HashMap<>();
Map<String, String> firstItems = gson.fromJson(firstRelease.getConfigurations(), configurationTypeReference); if (baseReleaseId != 0){
Map<String, String> secondItems = gson.fromJson(secondRelease.getConfigurations(), configurationTypeReference); ReleaseDTO baseRelease = releaseAPI.loadRelease(env, baseReleaseId);
baseReleaseConfiguration = gson.fromJson(baseRelease.getConfigurations(), configurationTypeReference);
}
if (toCompareReleaseId != 0){
ReleaseDTO toCompareRelease = releaseAPI.loadRelease(env, toCompareReleaseId);
toCompareReleaseConfiguration = gson.fromJson(toCompareRelease.getConfigurations(), configurationTypeReference);
}
ReleaseCompareResult compareResult = new ReleaseCompareResult(); ReleaseCompareResult compareResult = new ReleaseCompareResult();
//added and modified in firstRelease //added and modified in firstRelease
for (Map.Entry<String, String> entry : firstItems.entrySet()) { for (Map.Entry<String, String> entry : baseReleaseConfiguration.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
String firstValue = entry.getValue(); String firstValue = entry.getValue();
String secondValue = secondItems.get(key); String secondValue = toCompareReleaseConfiguration.get(key);
//added //added
if (secondValue == null) { if (secondValue == null) {
compareResult.addEntityPair(ChangeType.DELETED, new KVEntity(key, firstValue), compareResult.addEntityPair(ChangeType.DELETED, new KVEntity(key, firstValue),
new KVEntity(key, secondValue)); new KVEntity(key, null));
} else if (!Objects.equal(firstValue, secondValue)) { } else if (!Objects.equal(firstValue, secondValue)) {
compareResult.addEntityPair(ChangeType.MODIFIED, new KVEntity(key, firstValue), compareResult.addEntityPair(ChangeType.MODIFIED, new KVEntity(key, firstValue),
new KVEntity(key, secondValue)); new KVEntity(key, secondValue));
...@@ -124,10 +143,10 @@ public class ReleaseService { ...@@ -124,10 +143,10 @@ public class ReleaseService {
} }
//deleted in firstRelease //deleted in firstRelease
for (Map.Entry<String, String> entry : secondItems.entrySet()) { for (Map.Entry<String, String> entry : toCompareReleaseConfiguration.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
String value = entry.getValue(); String value = entry.getValue();
if (firstItems.get(key) == null) { if (baseReleaseConfiguration.get(key) == null) {
compareResult compareResult
.addEntityPair(ChangeType.ADDED, new KVEntity(key, ""), new KVEntity(key, value)); .addEntityPair(ChangeType.ADDED, new KVEntity(key, ""), new KVEntity(key, value));
} }
......
package com.ctrip.framework.apollo.portal.util;
import org.apache.commons.lang.time.FastDateFormat;
import java.util.Date;
public class RelativeDateFormat {
private static final FastDateFormat TIMESTAMP_FORMAT = FastDateFormat.getInstance("yyyyMMddHHmmss");
private static final long ONE_MINUTE = 60000L;
private static final long ONE_HOUR = 3600000L;
private static final long ONE_DAY = 86400000L;
private static final String ONE_SECOND_AGO = "秒前";
private static final String ONE_MINUTE_AGO = "分钟前";
private static final String ONE_HOUR_AGO = "小时前";
private static final String ONE_DAY_AGO = "天前";
private static final String ONE_MONTH_AGO = "月前";
public static String format(Date date) {
if (date.after(new Date())) {
throw new IllegalArgumentException("To format date can not after now");
}
long delta = new Date().getTime() - date.getTime();
if (delta < ONE_MINUTE) {
long seconds = toSeconds(delta);
return (seconds <= 0 ? 1 : seconds) + ONE_SECOND_AGO;
}
if (delta < 45L * ONE_MINUTE) {
long minutes = toMinutes(delta);
return (minutes <= 0 ? 1 : minutes) + ONE_MINUTE_AGO;
}
if (delta < 24L * ONE_HOUR) {
long hours = toHours(delta);
return (hours <= 0 ? 1 : hours) + ONE_HOUR_AGO;
}
if (delta < 48L * ONE_HOUR) {
return "昨天";
}
if (delta < 30L * ONE_DAY) {
long days = toDays(delta);
return (days <= 0 ? 1 : days) + ONE_DAY_AGO;
}
long months = toMonths(delta);
if (months <= 3) {
return (months <= 0 ? 1 : months) + ONE_MONTH_AGO;
} else {
return TIMESTAMP_FORMAT.format(date);
}
}
private static long toSeconds(long date) {
return date / 1000L;
}
private static long toMinutes(long date) {
return toSeconds(date) / 60L;
}
private static long toHours(long date) {
return toMinutes(date) / 60L;
}
private static long toDays(long date) {
return toHours(date) / 24L;
}
private static long toMonths(long date) {
return toDays(date) / 30L;
}
}
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
ng-submit="create()"> ng-submit="create()">
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label"> <label class="col-sm-2 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
部门</label> 部门</label>
<div class="col-sm-3"> <div class="col-sm-3">
<select id="organization"> <select id="organization">
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
</div> </div>
<div class="form-group" valdr-form-group> <div class="form-group" valdr-form-group>
<label class="col-sm-2 control-label"> <label class="col-sm-2 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
应用Id</label> 应用Id</label>
<div class="col-sm-3"> <div class="col-sm-3">
<input type="text" class="form-control" name="appId" ng-model="app.appId"> <input type="text" class="form-control" name="appId" ng-model="app.appId">
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
</div> </div>
<div class="form-group" valdr-form-group> <div class="form-group" valdr-form-group>
<label class="col-sm-2 control-label"> <label class="col-sm-2 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
应用名称</label> 应用名称</label>
<div class="col-sm-5"> <div class="col-sm-5">
<input type="text" class="form-control" name="appName" ng-model="app.name"> <input type="text" class="form-control" name="appName" ng-model="app.name">
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label"> <label class="col-sm-2 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
应用负责人</label> 应用负责人</label>
<div class="col-sm-6"> <div class="col-sm-6">
<apollouserselector apollo-id="userSelectWidgetId"></apollouserselector> <apollouserselector apollo-id="userSelectWidgetId"></apollouserselector>
......
...@@ -44,9 +44,9 @@ ...@@ -44,9 +44,9 @@
<button type="submit" class="btn btn-default" style="margin-left: 20px;" ng-disabled="submitBtnDisabled">添加</button> <button type="submit" class="btn btn-default" style="margin-left: 20px;" ng-disabled="submitBtnDisabled">添加</button>
</form> </form>
<!-- Split button --> <!-- Split button -->
<div class="user-container"> <div class="item-container">
<div class="btn-group user-info" ng-repeat="user in appRoleUsers.masterUsers"> <div class="btn-group item-info" ng-repeat="user in appRoleUsers.masterUsers">
<button type="button" class="btn btn-default" ng-bind="user.userId"></button> <button type="button" class="btn btn-default" ng-bind="user.userId"></button>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false" ng-click="removeMasterRoleFromUser(user.userId)"> aria-haspopup="true" aria-expanded="false" ng-click="removeMasterRoleFromUser(user.userId)">
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<body> <body>
<apollonav></apollonav> <apollonav></apollonav>
<div class="container-fluid apollo-container" ng-controller="ClusterController"> <div class="container-fluid apollo-container hidden" ng-controller="ClusterController">
<div class="row"> <div class="row">
<div class="col-md-8 col-md-offset-2"> <div class="col-md-8 col-md-offset-2">
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
<div class="panel-body"> <div class="panel-body">
<div class="alert alert-info" role="alert"> <div class="alert alert-info no-radius" role="alert">
<strong>Tips:</strong> <strong>Tips:</strong>
<ul> <ul>
<li>通过添加集群,可以使同一份程序在不同的集群(如不同的数据中心)使用不同的配置</li> <li>通过添加集群,可以使同一份程序在不同的集群(如不同的数据中心)使用不同的配置</li>
...@@ -51,14 +51,15 @@ ...@@ -51,14 +51,15 @@
ng-submit="create()"> ng-submit="create()">
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label"> <label class="col-sm-2 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
应用AppId</label> 应用AppId</label>
<div class="col-sm-6" ng-bind="appId"> <div class="col-sm-6">
<label class="form-control-static" ng-bind="appId"></label>
</div> </div>
</div> </div>
<div class="form-group" valdr-form-group> <div class="form-group" valdr-form-group>
<label class="col-sm-2 control-label"> <label class="col-sm-2 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
集群名称</label> 集群名称</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="text" class="form-control" name="clusterName" ng-model="clusterName"> <input type="text" class="form-control" name="clusterName" ng-model="clusterName">
...@@ -67,7 +68,7 @@ ...@@ -67,7 +68,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label"> <label class="col-sm-2 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
选择环境</label> 选择环境</label>
<div class="col-sm-5"> <div class="col-sm-5">
<table class="table table-hover" style="width: 100px"> <table class="table table-hover" style="width: 100px">
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
<link rel="icon" href="./img/config.png"> <link rel="icon" href="./img/config.png">
<link rel="stylesheet" type="text/css" href="vendor/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="vendor/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="vendor/angular/angular-toastr-1.4.1.min.css"> <link rel="stylesheet" type="text/css" href="vendor/angular/angular-toastr-1.4.1.min.css">
<link rel="stylesheet" type="text/css" href="vendor/select2/select2.min.css">
<link rel="stylesheet" type="text/css" media='all' href="vendor/angular/loading-bar.min.css"> <link rel="stylesheet" type="text/css" media='all' href="vendor/angular/loading-bar.min.css">
<link rel="stylesheet" type="text/css" href="styles/common-style.css"> <link rel="stylesheet" type="text/css" href="styles/common-style.css">
</head> </head>
...@@ -15,10 +16,9 @@ ...@@ -15,10 +16,9 @@
<apollonav></apollonav> <apollonav></apollonav>
<div class="container-fluid apollo-container app" id="config-info"> <div id="config-info" class="container-fluid apollo-container app">
<!--具体配置信息--> <!--具体配置信息-->
<div class="row config-info-container"> <div class="config-info-container row">
<div ng-controller="ConfigBaseInfoController"> <div ng-controller="ConfigBaseInfoController">
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
</p> </p>
</div> </div>
<div class="J_appFound hidden col-md-3 col-xs-3 col-sm-3" ng-show="!notFoundApp"> <div class="J_appFound hidden col-md-2 col-xs-2 col-sm-2" ng-show="!notFoundApp">
<section class="panel"> <section class="panel">
<header class="panel-heading"> <header class="panel-heading">
<img src="img/env.png" class="i-20">&nbsp;环境列表 <img src="img/env.png" class="i-20">&nbsp;环境列表
...@@ -67,7 +67,9 @@ ...@@ -67,7 +67,9 @@
</tr> </tr>
<tr> <tr>
<th>应用名:</th> <th>应用名:</th>
<td ng-bind="appBaseInfo.name"></td> <td>
<small ng-bind="appBaseInfo.name"></small>
</td>
</tr> </tr>
<tr> <tr>
<th>部门:</th> <th>部门:</th>
...@@ -78,8 +80,10 @@ ...@@ -78,8 +80,10 @@
<td ng-bind="appBaseInfo.ownerName"></td> <td ng-bind="appBaseInfo.ownerName"></td>
</tr> </tr>
<tr> <tr>
<th>负责人Email:</th> <th>Email:</th>
<td ng-bind="appBaseInfo.ownerEmail"></td> <td>
<small ng-bind="appBaseInfo.ownerEmail"></small>
</td>
</tr> </tr>
<tr ng-show="missEnvs.length > 0"> <tr ng-show="missEnvs.length > 0">
<th>缺失的环境:</th> <th>缺失的环境:</th>
...@@ -129,8 +133,10 @@ ...@@ -129,8 +133,10 @@
</div> </div>
</div> </div>
<!--namespaces--> <!--namespaces-->
<div class="col-md-9 col-xs-9 col-sm-9 config-item-container hide" ng-controller="ConfigNamespaceController"> <div class="col-md-10 col-xs-10 col-sm-10 config-item-container hide"
ng-controller="ConfigNamespaceController">
<div class="alert alert-warning alert-dismissible" role="alert" <div class="alert alert-warning alert-dismissible" role="alert"
ng-show="(!hideTip || !hideTip[pageContext.appId][pageContext.clusterName]) && envMapClusters[pageContext.env]"> ng-show="(!hideTip || !hideTip[pageContext.appId][pageContext.clusterName]) && envMapClusters[pageContext.env]">
...@@ -159,20 +165,57 @@ ...@@ -159,20 +165,57 @@
</div> </div>
<div ng-repeat="namespace in namespaces"> <apollonspanel ng-repeat="namespace in namespaces"
<apollonspanel namespace="namespace" app-id="pageContext.appId" namespace="namespace"
env="pageContext.env" cluster="pageContext.clusterName" app-id="pageContext.appId"
env="pageContext.env"
lock-check="lockCheck" lock-check="lockCheck"
cluster="pageContext.clusterName"
user="currentUser"
pre-release-ns="prepareReleaseNamespace" pre-release-ns="prepareReleaseNamespace"
create-item="createItem" edit-item="editItem" create-item="createItem" edit-item="editItem"
pre-delete-item="preDeleteItem" commit-change="commitChange" pre-delete-item="preDeleteItem"
pre-rollback="preRollback" show-text="showText" show-text="showText"
show-no-modify-permission-dialog="showNoModifyPermissionDialog"></apollonspanel> show-no-modify-permission-dialog="showNoModifyPermissionDialog"
</div> pre-create-branch="preCreateBranch"
pre-delete-branch="preDeleteBranch">
</apollonspanel>
<releasemodal app-id="pageContext.appId"
env="pageContext.env"
cluster="pageContext.clusterName">
</releasemodal>
<itemmodal to-operation-namespace="toOperationNamespace"
app-id="pageContext.appId"
env="pageContext.env"
cluster="pageContext.clusterName"
item="item">
</itemmodal>
<showtextmodal text="text"></showtextmodal>
<rollbackmodal app-id="pageContext.appId"
env="pageContext.env"
cluster="pageContext.clusterName">
</rollbackmodal>
<rulesmodal app-id="pageContext.appId"
env="pageContext.env"
cluster="pageContext.clusterName">
</rulesmodal>
<mergeandpublishmodal app-id="pageContext.appId"
env="pageContext.env"
cluster="pageContext.clusterName">
</mergeandpublishmodal>
<!-- delete modal-->
<apolloconfirmdialog apollo-dialog-id="'deleteConfirmDialog'" apollo-title="'删除配置'" <apolloconfirmdialog apollo-dialog-id="'deleteConfirmDialog'" apollo-title="'删除配置'"
apollo-detail="'确定要删除配置吗?'" apollo-confirm="deleteItem"></apolloconfirmdialog> apollo-detail="'确定要删除配置吗?'"
apollo-show-cancel-btn="true" apollo-confirm="deleteItem"></apolloconfirmdialog>
<apolloconfirmdialog apollo-dialog-id="'releaseNoPermissionDialog'" apollo-title="'发布'" <apolloconfirmdialog apollo-dialog-id="'releaseNoPermissionDialog'" apollo-title="'发布'"
apollo-detail="'您没有发布权限哦~ 请找项目管理员 ' + masterUsers + ' 分配发布权限'" apollo-detail="'您没有发布权限哦~ 请找项目管理员 ' + masterUsers + ' 分配发布权限'"
...@@ -183,350 +226,70 @@ ...@@ -183,350 +226,70 @@
apollo-show-cancel-btn="false"></apolloconfirmdialog> apollo-show-cancel-btn="false"></apolloconfirmdialog>
<apolloconfirmdialog apollo-dialog-id="'masterNoPermissionDialog'" apollo-title="'申请配置权限'" <apolloconfirmdialog apollo-dialog-id="'masterNoPermissionDialog'" apollo-title="'申请配置权限'"
apollo-detail="'您不是项目管理员, 只有项目管理员才有添加集群、namespace的权限。 apollo-detail="'您不是项目管理员 只有项目管理员才有添加集群、namespace的权限。
如需管理员权限,请找项目管理员 ' + masterUsers + ' 分配管理员权限'" 如需管理员权限请找项目管理员 ' + masterUsers + ' 分配管理员权限'"
apollo-show-cancel-btn="false"></apolloconfirmdialog> apollo-show-cancel-btn="false"></apolloconfirmdialog>
<apolloconfirmdialog apollo-dialog-id="'namespaceLockedDialog'" apollo-title="'编辑受限'" <apolloconfirmdialog apollo-dialog-id="'namespaceLockedDialog'" apollo-title="'编辑受限'"
apollo-detail="'当前namespace正在被 ' + lockOwner + ' 编辑, 一次发布只能被一个人修改.'" apollo-detail="'当前namespace正在被 ' + lockOwner + ' 编辑一次发布只能被一个人修改.'"
apollo-show-cancel-btn="false"></apolloconfirmdialog> apollo-show-cancel-btn="false"></apolloconfirmdialog>
<apolloconfirmdialog apollo-dialog-id="'releaseDenyDialog'" apollo-title="'发布受限'" <apolloconfirmdialog apollo-dialog-id="'releaseDenyDialog'" apollo-title="'发布受限'"
apollo-detail="'您不能发布哟~ 编辑和发布不能为同一个人'" apollo-detail="'您不能发布哟~ ' + pageContext.env + '环境配置的编辑和发布必须为不同的人,请找另一个具有当前namespace发布权的人操作发布~'"
apollo-show-cancel-btn="false"></apolloconfirmdialog> apollo-show-cancel-btn="false"></apolloconfirmdialog>
<apolloconfirmdialog apollo-dialog-id="'rollbackAlertDialog'" apollo-title="'回滚'" <apolloconfirmdialog apollo-dialog-id="'rollbackAlertDialog'" apollo-title="'回滚'"
apollo-detail="'确定要回滚吗?'" apollo-detail="'确定要回滚吗?'"
apollo-show-cancel-btn="true" apollo-confirm="rollback"></apolloconfirmdialog> apollo-show-cancel-btn="true" apollo-confirm="rollback"></apolloconfirmdialog>
<apolloconfirmdialog apollo-dialog-id="'deleteBranchDialog'" apollo-title="'删除灰度'"
apollo-detail="'删除灰度会丢失灰度的配置,确定要删除吗?'"
apollo-show-cancel-btn="true" apollo-confirm="deleteBranch"></apolloconfirmdialog>
<div class="modal fade" id="showText" tabindex="-1" role="dialog"> <apolloconfirmdialog apollo-dialog-id="'updateRuleTips'" apollo-title="'更新灰度规则提示'"
<div class="modal-dialog" style="width: 960px;"> apollo-detail="'灰度规则已生效,但发现灰度版本有未发布的配置,这些配置需要手动灰度发布才会生效'"></apolloconfirmdialog>
<div class="modal-content no-radius">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<button class="btn close clipboard" data-clipboard-text="{{text}}" data-tooltip="tooltip"
data-placement="bottom" title="复制">
<img class="i-20" src="img/clippy.svg" style="margin-right: 5px;margin-top: -2px;">
</button>
<h4 class="modal-title">查看</h4>
</div>
<pre id="watchText" class="modal-body no-radius" style="margin-bottom: 0" ng-bind="text"></pre>
</div>
</div>
</div>
<!--create release modal-->
<form class="modal fade form-horizontal" name="releaseForm" valdr-type="Release" id="releaseModal"
tabindex="-1" role="dialog"
ng-submit="release()">
<div class="modal-dialog" role="document" style="width: 960px">
<div class="modal-content">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">发布</h4>
</div>
<div class="modal-body">
<div class="form-group">
<div class="col-sm-2 control-label" ng-if="!toReleaseNamespace.isPropertiesFormat">
<div class="row">
<div class="btn-group btn-group-xs" style="padding-right: 10px" role="group">
<button type="button" class="btn btn-default"
ng-class="{active:releaseChangeViewType=='change'}"
ng-click="switchReleaseChangeViewType('change')">查看变更
</button>
<button type="button" class="btn btn-default"
ng-class="{active:releaseChangeViewType=='release'}"
ng-click="switchReleaseChangeViewType('release')">发布的值
</button>
</div>
</div>
</div>
<label class="col-sm-2 control-label" ng-if="toReleaseNamespace.isPropertiesFormat">Changes</label>
<div class="col-sm-10" ng-if="toReleaseNamespace.itemModifiedCnt" valdr-form-group>
<!--properites format-->
<table class="table table-bordered table-striped text-center table-hover"
ng-show="toReleaseNamespace.itemModifiedCnt"
ng-if="toReleaseNamespace.isPropertiesFormat">
<thead>
<tr>
<th>
Key
</th>
<th>
Old Value
</th>
<th>
New Value
</th>
<th>
最后修改人
</th>
<th>
最后修改时间
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="config in toReleaseNamespace.items"
ng-if="config.item.key && config.isModified">
<td width="20%" title="{{config.item.key}}">
<span class="label label-danger"
ng-show="config.isDeleted">deleted</span>
<span ng-bind="config.item.key"></span>
</td>
<td width="25%" title="{{config.oldValue}}">
<span ng-bind="config.oldValue"></span>
</td>
<td width="25%" title="{{config.newValue}}">
<span ng-bind="config.newValue"></span>
</td>
<td width="15%" ng-bind="config.item.dataChangeLastModifiedBy">
</td>
<td width="15%"
ng-bind="config.item.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'">
</td>
</tr>
</tbody>
</table>
<!--file format -->
<div ng-repeat="item in toReleaseNamespace.items"
ng-if="!toReleaseNamespace.isPropertiesFormat"
ng-show="releaseChangeViewType=='change'">
<apollodiff old-str="item.oldValue" new-str="item.newValue"
apollo-id="'releaseStrDiff'"></apollodiff>
</div>
<div ng-repeat="item in toReleaseNamespace.items"
ng-if="!toReleaseNamespace.isPropertiesFormat"
ng-show="releaseChangeViewType=='release'">
<textarea class="form-control" rows="20" style="border-radius: 0px"
ng-disabled="true" ng-show="item.newValue" ng-bind="item.newValue">
</textarea>
</div>
</div> <apolloconfirmdialog apollo-dialog-id="'mergeAndReleaseDenyDialog'" apollo-title="'全量发布'"
<div class="col-sm-5" valdr-form-group> apollo-detail="'namespace主版本有未发布的配置,请先发布主版本配置'"></apolloconfirmdialog>
<span ng-show="!toReleaseNamespace.itemModifiedCnt">
配置没有变化
</span>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">
<apollorequiredfiled></apollorequiredfiled>
Release Name:</label>
<div class="col-sm-5" valdr-form-group>
<input type="text" name="releaseName" class="form-control"
placeholder="input release name"
ng-model="releaseTitle" ng-required="true">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Comment:</label>
<div class="col-sm-10" valdr-form-group>
<textarea rows="4" name="comment" class="form-control"
style="margin-top: 15px;"
ng-model="releaseComment"
placeholder="Add an optional extended description..."></textarea>
</div>
</div>
<apolloconfirmdialog apollo-dialog-id="'grayReleaseWithoutRulesTips'" apollo-title="'缺失灰度规则提示'"
apollo-detail="'灰度版本还没有配置任何灰度规则,请配置灰度规则'">
</apolloconfirmdialog>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
<button type="submit" class="btn btn-primary"
ng-disabled="releaseForm.$invalid || releaseBtnDisabled">发布
</button>
</div>
</div>
</div>
</form>
<!--table mode item modal--> <div class="modal fade" id="createBranchTips" tabindex="-1" role="dialog">
<form class="modal fade form-horizontal" name="itemForm" valdr-type="Item" id="itemModal" role="dialog"
ng-submit="doItem()">
<div class="modal-dialog" role="document"> <div class="modal-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header panel-primary"> <div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button> aria-hidden="true">&times;</span></button>
<h4 class="modal-title"> <h4 class="modal-title">创建灰度须知</h4>
<span ng-show="tableViewOperType == 'create'"> 添加配置项</span>
<span ng-show="tableViewOperType == 'update'"> 修改配置项</span>
</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label class="col-sm-2 control-label">
<apollorequiredfiled
ng-show="tableViewOperType == 'create'"></apollorequiredfiled>
Key
</label>
<div class="col-sm-10" valdr-form-group>
<input type="text" name="key" class="form-control" ng-model="item.key"
ng-required="true" ng-disabled="tableViewOperType != 'create'">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">
<apollorequiredfiled></apollorequiredfiled>
Value
</label>
<div class="col-sm-10" valdr-form-group>
<textarea type="text" name="value" class="form-control" rows="6"
ng-model="item.value">
</textarea>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Comment</label>
<div class="col-sm-10" valdr-form-group>
<textarea class="form-control" name="comment" ng-model="item.comment"
rows="2">
</textarea>
</div>
</div>
<div class="form-group" ng-show="tableViewOperType == 'create'">
<label class="col-sm-2 control-label">
<apollorequiredfiled></apollorequiredfiled>
选择集群</label>
<div class="col-sm-10">
<apolloclusterselector apollo-app-id="pageContext.appId"
apollo-default-all-checked="false"
apollo-default-checked-env="pageContext.env"
apollo-default-checked-cluster="pageContext.clusterName"
apollo-select="collectSelectedClusters">
</apolloclusterselector>
</div>
</div>
</div>
<div class="modal-footer">
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">
关闭
</button>
<button type="submit" class="btn btn-primary"
ng-disabled="itemForm.$invalid || (addItemBtnDisabled && tableViewOperType == 'create')">
提交
</button>
</div>
</div>
</div>
</form>
<!--rollback-->
<form class="modal fade form-horizontal" id="rollbackModal" tabindex="-1" role="dialog"
ng-submit="showRollbackAlertDialog()">
<div class="modal-dialog" role="document" style="width: 960px">
<div class="modal-content">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<div class="modal-title text-center">
<span style="font-size: 18px;" ng-bind="firstRelease.name"></span>
<span style="font-size: 18px;"> &nbsp;回滚到&nbsp;</span>
<span style="font-size: 18px;" ng-bind="secondRelease.name"></span>
</div>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="alert alert-warning" role="alert"> 通过创建灰度版本,您可以对某些配置做灰度测试<br>
此操作将会回滚到上一个发布版本,且当前版本作废,但不影响正在修改的配置。可在发布历史页面查看当前生效的版本 灰度流程为:<br>
<a target="_blank" &nbsp;&nbsp;1.创建灰度版本 <br>
href="/config/history.html?#/appid={{toRollbackNamespace.baseInfo.appId}}&env={{pageContext.env}}&clusterName={{toRollbackNamespace.baseInfo.clusterName}}&namespaceName={{toRollbackNamespace.baseInfo.namespaceName}}">点击查看</a> &nbsp;&nbsp;2.配置灰度配置项<br>
</div> &nbsp;&nbsp;3.配置灰度规则.如果是私有的namespace可以按照客户端的IP进行灰度,如果是公共的namespace则可以同时按AppId和客户端的IP进行灰度<br>
&nbsp;&nbsp;4.灰度发布<br>
<div class="form-group" style="margin-top: 15px;"> 灰度版本最终有两种结果:<b>全量发布和放弃灰度</b><br>
<!--properties format--> <b>全量发布</b>:灰度的配置合到主版本并发布,所有的客户端都会使用合并后的配置<br>
<div class="col-sm-12" <b>放弃灰度</b>:删除灰度版本,所有的客户端都会使用回主版本的配置<br>
ng-if="releaseCompareResult.length > 0 && toRollbackNamespace.isPropertiesFormat"> 注意事项:<br>
<table class="table table-bordered table-striped text-center table-hover" &nbsp;&nbsp;1.如果灰度版本已经有灰度发布过,那么修改灰度规则后,无需再次灰度发布就立即生效<br>
ng-if="toRollbackNamespace.isPropertiesFormat">
<thead>
<tr>
<th>
Type
</th>
<th>
Key
</th>
<th>
回滚前
</th>
<th>
回滚后
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="change in releaseCompareResult">
<td width="10%">
<span ng-show="change.type == 'ADDED'">新增</span>
<span ng-show="change.type == 'MODIFIED'">更新</span>
<span ng-show="change.type == 'DELETED'">删除</span>
</td>
<td width="20%" ng-bind="change.entity.firstEntity.key">
</td>
<td width="35%" ng-bind="change.entity.firstEntity.value">
</td>
<td width="35%" ng-bind="change.entity.secondEntity.value">
</td>
</tr>
</tbody>
</table>
</div>
<!--file format -->
<div class="col-sm-12"
ng-if="releaseCompareResult.length > 0 && !toRollbackNamespace.isPropertiesFormat">
<div ng-repeat="change in releaseCompareResult"
ng-if="!toRollbackNamespace.isPropertiesFormat">
<h5>回滚前</h5>
<textarea class="form-control no-radius" rows="20"
ng-disabled="true" ng-bind="change.entity.firstEntity.value">
</textarea>
<hr>
<h5>回滚后</h5>
<textarea class="form-control no-radius" rows="20"
ng-disabled="true" ng-bind="change.entity.secondEntity.value">
</textarea>
</div>
</div>
<div class="col-sm-12 text-center" ng-if="releaseCompareResult.length == 0">
<h4>
配置没有变化
</h4>
</div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button> <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="submit" class="btn btn-danger" ng-if="releaseCompareResult.length > 0" <button type="button" class="btn btn-primary" data-dismiss="modal"
ng-disabled="rollbackBtnDisabled">回滚 ng-click="createBranch()">
确定
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</form>
</div> </div>
</div> </div>
</div>
</div> </div>
...@@ -536,6 +299,8 @@ ...@@ -536,6 +299,8 @@
<!-- jquery.js --> <!-- jquery.js -->
<script src="vendor/jquery.min.js" type="text/javascript"></script> <script src="vendor/jquery.min.js" type="text/javascript"></script>
<script src="vendor/select2/select2.min.js" type="text/javascript"></script>
<!--lodash.js--> <!--lodash.js-->
<script src="vendor/lodash.min.js" type="text/javascript"></script> <script src="vendor/lodash.min.js" type="text/javascript"></script>
...@@ -575,6 +340,8 @@ ...@@ -575,6 +340,8 @@
<script type="application/javascript" src="scripts/services/NamespaceLockService.js"></script> <script type="application/javascript" src="scripts/services/NamespaceLockService.js"></script>
<script type="application/javascript" src="scripts/services/InstanceService.js"></script> <script type="application/javascript" src="scripts/services/InstanceService.js"></script>
<script type="application/javascript" src="scripts/services/FavoriteService.js"></script> <script type="application/javascript" src="scripts/services/FavoriteService.js"></script>
<script type="application/javascript" src="scripts/services/NamespaceBranchService.js"></script>
<script type="application/javascript" src="scripts/services/EventManager.js"></script>
<script type="application/javascript" src="scripts/AppUtils.js"></script> <script type="application/javascript" src="scripts/AppUtils.js"></script>
...@@ -583,6 +350,12 @@ ...@@ -583,6 +350,12 @@
<script type="application/javascript" src="scripts/directive/directive.js"></script> <script type="application/javascript" src="scripts/directive/directive.js"></script>
<script type="application/javascript" src="scripts/directive/namespace-panel-directive.js"></script> <script type="application/javascript" src="scripts/directive/namespace-panel-directive.js"></script>
<script type="application/javascript" src="scripts/directive/diff-directive.js"></script> <script type="application/javascript" src="scripts/directive/diff-directive.js"></script>
<script type="application/javascript" src="scripts/directive/release-modal-directive.js"></script>
<script type="application/javascript" src="scripts/directive/item-modal-directive.js"></script>
<script type="application/javascript" src="scripts/directive/show-text-modal-directive.js"></script>
<script type="application/javascript" src="scripts/directive/rollback-modal-directive.js"></script>
<script type="application/javascript" src="scripts/directive/gray-release-rules-modal-directive.js"></script>
<script type="application/javascript" src="scripts/directive/merge-and-publish-modal-directive.js"></script>
<!--controller--> <!--controller-->
<script type="application/javascript" src="scripts/controller/config/ConfigNamespaceController.js"></script> <script type="application/javascript" src="scripts/controller/config/ConfigNamespaceController.js"></script>
......
...@@ -15,81 +15,231 @@ ...@@ -15,81 +15,231 @@
<apollonav></apollonav> <apollonav></apollonav>
<div class="container-fluid apollo-container release-history" ng-controller="ReleaseHistoryController"> <div class="container-fluid apollo-container" ng-controller="ReleaseHistoryController">
<section class="panel col-md-offset-1 col-md-10"> <section class="release-history panel col-md-12 no-radius hidden">
<div class="panel-heading row"> <div class="panel-heading row">
<div class="col-md-4">
<div class="operation-caption-container col-md-3">
<div class="operation-caption release-operation-normal text-center"
style="left:0;">
<small>主版本发布</small>
</div>
<div class="operation-caption release-operation-rollback text-center"
style="left: 80px;">
<small>主版本回滚</small>
</div>
<div class="operation-caption release-operation-gray text-center"
style="left: 160px;">
<small>灰度操作</small>
</div>
</div>
<div class="col-md-6 text-center">
<h4>发布历史</h4> <h4>发布历史</h4>
<small>(AppId:{{pageContext.appId}}, ENV:{{pageContext.env}}, Cluster:{{pageContext.clusterName}},
Namespace:{{pageContext.namespaceName}})
</small>
</div> </div>
<div class="col-md-8 text-right">
<div class="pull-right back-btn">
<a type="button" class="btn btn-info" href="/config.html?#/appid={{pageContext.appId}}">返回到项目首页 <a type="button" class="btn btn-info" href="/config.html?#/appid={{pageContext.appId}}">返回到项目首页
</a> </a>
</div> </div>
</div> </div>
<div class="panel-body">
<div class="media" ng-repeat="release in releases"> <div class="release-history-container panel-body row" ng-show="releaseHistories && releaseHistories.length > 0">
<div class="media-left media-middle badge-{{$index % 4}}"> <div class="release-history-list col-md-3">
<div class="media hover" ng-class="{'active': releaseHistory.id == selectedReleaseHistory}"
ng-repeat="releaseHistory in releaseHistories"
ng-click="showReleaseHistoryDetail(releaseHistory)">
<div class="release-operation"
ng-class="{'release-operation-normal': releaseHistory.operation == 0 || releaseHistory.operation == 5,
'release-operation-gray': releaseHistory.operation == 2 || releaseHistory.operation == 3 ||
releaseHistory.operation == 4 || releaseHistory.operation == 7 || releaseHistory.operation == 8,
'release-operation-rollback': releaseHistory.operation == 1 || releaseHistory.operation == 6}">
</div> </div>
<h4 class="media-left text-center" ng-bind="releaseHistory.operator">
</h4>
<div class="media-body"> <div class="media-body">
<div class="row text-right">
<span class="label label-info no-radius" <h5 class="col-md-7 word-break" ng-show="releaseHistory.operation == 0">普通发布</h5>
ng-show="release.baseInfo.isAbandoned">已废弃</span> <h5 class="col-md-7 word-break" ng-show="releaseHistory.operation == 1">回滚</h5>
<span class="label label-primary no-radius" <h5 class="col-md-7 word-break" ng-show="releaseHistory.operation == 2">灰度发布</h5>
ng-if="release.active">当前生效</span> <h5 class="col-md-7 word-break" ng-show="releaseHistory.operation == 3">更新灰度规则</h5>
<h5 class="col-md-7 word-break" ng-show="releaseHistory.operation == 4">灰度全量发布</h5>
<h5 class="col-md-7 word-break" ng-show="releaseHistory.operation == 5">灰度发布(主版本发布)</h5>
<h5 class="col-md-7 word-break" ng-show="releaseHistory.operation == 6">灰度发布(主版本回滚)</h5>
<h5 class="col-md-7 word-break" ng-show="releaseHistory.operation == 7">放弃灰度</h5>
<h5 class="col-md-7 word-break" ng-show="releaseHistory.operation == 8">删除灰度(全量发布)</h5>
<h6 class="col-md-5 text-right" ng-bind="releaseHistory.releaseTimeFormatted"></h6>
</div> </div>
<div class="row" id="{{release.baseInfo.id}}">
<div class="col-md-2 user">
<img src="../img/user.png" class="i-20">
<span class="info" ng-bind="release.baseInfo.dataChangeCreatedBy"></span>
</div> </div>
<div class="col-md-3 time">
<img src="../img/time.png" class="i-20"> <div class="load-more media panel-heading text-center hover"
<span class="info" ng-show="!hasLoadAll"
ng-bind="release.baseInfo.dataChangeCreatedTime | date: 'yyyy-MM-dd HH:mm:ss'"></span> ng-click="findReleaseHistory()">
加载更多
</div>
</div>
<!--properties mode info-->
<div class="release-info col-md-9 panel panel-default no-radius"
ng-show="!isTextNamespace">
<div class="panel-heading">
<span ng-bind="history.releaseTitle"></span>
<span class="pull-right" ng-bind="history.releaseTime | date: 'yyyy-MM-dd HH:mm:ss'"></span>
<div class="row" style="padding-top: 10px;">
<div class="col-md-5">
<small ng-show="history.releaseComment" ng-bind="history.releaseComment"></small>
</div>
<div class="col-md-7 text-right">
<div class="btn-group">
<button type="button" class="btn btn-default btn-sm"
ng-class="{'active':history.viewType == 'diff'}"
data-tooltip="tooltip" data-placement="bottom" title="查看此次发布与上次版本的变更"
ng-click="switchConfigViewType(history, 'diff')">变更的配置
</button>
<button type="button" class="btn btn-default btn-sm"
ng-class="{'active':history.viewType == 'all'}"
data-tooltip="tooltip" data-placement="bottom" title="查看此次发布的所有配置信息"
ng-click="switchConfigViewType(history, 'all')">全部配置
</button>
</div>
</div> </div>
<div class="col-md-3 time">
<img src="../img/title.png" class="i-20">
<span class="info"
ng-bind="release.baseInfo.name" ng-class="{'highlight':pageContext.scrollTo == release.baseInfo.id}"></span>
</div> </div>
<div class="col-md-4 comment" ng-show="release.baseInfo.comment">
<img src="../img/comment.png" class="i-20">
<span class="info" ng-bind="release.baseInfo.comment"></span>
</div> </div>
<div class="panel-body config">
<section ng-show="history.viewType=='diff'">
<h4 class="section-title">变更的配置</h4>
<div ng-show="history.changes && history.changes.length > 0">
<table class="no-margin table table-striped table-hover table-bordered">
<thead>
<tr>
<th>Type</th>
<th>Key</th>
<th>Old Value</th>
<th>New Value</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="change in history.changes">
<td width="10%">
<span ng-show="change.type == 'ADDED'">新增</span>
<span ng-show="change.type == 'MODIFIED'">修改</span>
<span ng-show="change.type == 'DELETED'">删除</span>
</td>
<td class="cursor-pointer" width="20%"
ng-click="showText(change.entity.firstEntity.key)">
<span ng-bind="change.entity.firstEntity.key | limitTo: 250"></span>
<span ng-bind="change.entity.firstEntity.key.length > 250 ? '...' :''"></span>
</td>
<td class="cursor-pointer" width="35%"
ng-click="showText(change.entity.firstEntity.value)">
<span ng-bind="change.entity.firstEntity.value | limitTo: 250"></span>
<span ng-bind="change.entity.firstEntity.value.length > 250 ? '...' :''"></span>
</td>
<td class="cursor-pointer" width="35%"
ng-click="showText(change.entity.secondEntity.value)">
<span ng-bind="change.entity.secondEntity.value | limitTo: 250"></span>
<span ng-bind="change.entity.secondEntity.value.length > 250 ? '...' :''"></span>
</td>
</tr>
</tbody>
</table>
</div> </div>
<div class="text-center empty-container"
ng-show="!history.changes || history.changes.length == 0">
<h5>无配置更改</h5>
</div>
</section>
<table class="table table-hover table-bordered table-striped" ng-show="!isTextFile">
<tr ng-repeat="item in release.items">
<td width="30%" ng-bind="item.key">
<section ng-show="history.viewType=='all'">
<h4 class="section-title">全部配置</h4>
<table class="no-margin table table-striped table-hover table-bordered"
ng-show="history.configuration && history.configuration.length > 0">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in history.configuration">
<td class="cursor-pointer" width="30%" ng-click="showText(item.firstEntity)">
<span ng-bind="item.firstEntity | limitTo: 250"></span>
<span ng-bind="item.firstEntity.length > 250 ? '...' :''"></span>
</td> </td>
<td width="70%" ng-bind="item.value"> <td class="cursor-pointer" width="70%" ng-click="showText(item.secondEntity)">
<span ng-bind="item.secondEntity | limitTo: 250"></span>
<span ng-bind="item.secondEntity.length > 250 ? '...' :''"></span>
</td> </td>
</tr> </tr>
</tbody>
</table>
<div class="text-center empty-container"
ng-show="history.viewType=='all' && (!history.configuration || history.configuration.length == 0)">
<h5>无配置</h5>
</div>
</section>
<section ng-show="history.branchName != history.clusterName && history.operation != 8 && history.operation != 7">
<hr>
<h4 class="section-title">灰度规则</h4>
<table class="no-margin table table-striped table-hover table-bordered"
ng-show="history.operationContext.rules">
<thead>
<tr>
<th>灰度的AppId</th>
<th>灰度的IP</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="rule in history.operationContext.rules">
<td width="20%" ng-bind="rule.clientAppId"></td>
<td width="80%" ng-bind="rule.clientIpList.join(', ')"></td>
</tr>
</tbody>
</table> </table>
<textarea class="form-control no-radius" rows="15" ng-show="isTextFile" <h5 class="text-center empty-container" ng-show="!history.operationContext.rules">
ng-bind="release.items[0].value" disabled> 无灰度规则
</h5>
</section>
</textarea>
</div> </div>
</div> </div>
<div class="text-center load-more">
<button type="button" class="btn btn-default" ng-show="!hasLoadAll" <!--text mode-->
ng-click="loadMore()">加载更多 <div class="release-info col-md-9"
<span class="glyphicon glyphicon-menu-down"></span> ng-show="isTextNamespace && history.changes && history.changes.length > 0">
</button> <apollodiff ng-repeat="change in history.changes"
old-str="change.entity.firstEntity.value"
new-str="change.entity.secondEntity.value"
apollo-id="'releaseStrDiff'">
</apollodiff>
</div> </div>
<div class="text-center" ng-show="!releases.length">
<h4>还没发布过哟~</h4>
</div> </div>
<div class="panel-body" ng-show="!releaseHistories || releaseHistories.length == 0">
<h4 class="text-center empty-container">无发布历史信息</h4>
</div> </div>
</section> </section>
<showtextmodal text="text"></showtextmodal>
</div> </div>
<div ng-include="'../views/common/footer.html'"></div> <div ng-include="'../views/common/footer.html'"></div>
<!-- jquery.js --> <!-- jquery.js -->
...@@ -108,16 +258,23 @@ ...@@ -108,16 +258,23 @@
<!--nicescroll--> <!--nicescroll-->
<script src="../vendor/jquery.nicescroll.min.js"></script> <script src="../vendor/jquery.nicescroll.min.js"></script>
<script src="../vendor/diff.min.js" type="text/javascript"></script>
<!--biz--> <!--biz-->
<script type="application/javascript" src="../scripts/app.js"></script> <script type="application/javascript" src="../scripts/app.js"></script>
<script type="application/javascript" src="../scripts/services/AppService.js"></script> <script type="application/javascript" src="../scripts/services/AppService.js"></script>
<script type="application/javascript" src="../scripts/services/EnvService.js"></script> <script type="application/javascript" src="../scripts/services/EnvService.js"></script>
<script type="application/javascript" src="../scripts/services/ReleaseService.js"></script> <script type="application/javascript" src="../scripts/services/ReleaseService.js"></script>
<script type="application/javascript" src="../scripts/services/UserService.js"></script> <script type="application/javascript" src="../scripts/services/UserService.js"></script>
<script type="application/javascript" src="../scripts/services/ReleaseHistoryService.js"></script>
<script type="application/javascript" src="../scripts/services/ConfigService.js"></script>
<script type="application/javascript" src="../scripts/AppUtils.js"></script> <script type="application/javascript" src="../scripts/AppUtils.js"></script>
<script type="application/javascript" src="../scripts/controller/config/ReleaseHistoryController.js"></script> <script type="application/javascript" src="../scripts/controller/config/ReleaseHistoryController.js"></script>
<script type="application/javascript" src="../scripts/PageCommon.js"></script> <script type="application/javascript" src="../scripts/PageCommon.js"></script>
<script type="application/javascript" src="../scripts/directive/directive.js"></script> <script type="application/javascript" src="../scripts/directive/directive.js"></script>
<script type="application/javascript" src="../scripts/directive/show-text-modal-directive.js"></script>
<script type="application/javascript" src="../scripts/directive/diff-directive.js"></script>
</body> </body>
</html> </html>
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<apollonav></apollonav> <apollonav></apollonav>
<div class="container-fluid apollo-container" ng-controller="SyncItemController"> <div class="container-fluid apollo-container hidden" ng-controller="SyncItemController">
<section class="panel col-md-offset-1 col-md-10"> <section class="panel col-md-offset-1 col-md-10">
<header class="panel-heading"> <header class="panel-heading">
<div class="row"> <div class="row">
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
</header> </header>
<div class="panel-body"> <div class="panel-body">
<div class="row" ng-show="syncItemStep == 1"> <div class="row" ng-show="syncItemStep == 1">
<div class="alert-info alert"> <div class="alert-info alert no-radius">
<strong>Tips:</strong> <strong>Tips:</strong>
<ul> <ul>
<li>通过同步配置功能,可以使多个环境、集群间的配置保持一致</li> <li>通过同步配置功能,可以使多个环境、集群间的配置保持一致</li>
...@@ -204,24 +204,7 @@ ...@@ -204,24 +204,7 @@
</div> </div>
</section> </section>
<div class="modal fade" id="showText" tabindex="-1" role="dialog"> <showtextmodal text="text"/>
<div class="modal-dialog" style="width: 960px;">
<div class="modal-content no-radius">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<button class="btn close clipboard" data-clipboard-text="{{text}}" data-tooltip="tooltip"
data-placement="bottom" title="复制">
<img class="i-20" src="../img/clippy.svg" style="margin-right: 5px;margin-top: -2px;">
</button>
<h4 class="modal-title">查看</h4>
</div>
<pre id="watchText" class="modal-body no-radius" style="margin-bottom: 0" ng-bind="text"></pre>
</div>
</div>
</div>
</div> </div>
...@@ -256,5 +239,6 @@ ...@@ -256,5 +239,6 @@
<script type="application/javascript" src="../scripts/PageCommon.js"></script> <script type="application/javascript" src="../scripts/PageCommon.js"></script>
<script type="application/javascript" src="../scripts/directive/directive.js"></script> <script type="application/javascript" src="../scripts/directive/directive.js"></script>
<script type="application/javascript" src="../scripts/directive/show-text-modal-directive.js"></script>
</body> </body>
</html> </html>
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<apollonav></apollonav> <apollonav></apollonav>
<div id="app-list" ng-controller="IndexController"> <div id="app-list" class="hidden" ng-controller="IndexController">
<section class="media create-app-list"> <section class="media create-app-list">
<aside class="media-left text-center"> <aside class="media-left text-center">
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<apollonav></apollonav> <apollonav></apollonav>
<div class="container-fluid apollo-container" ng-controller="LinkNamespaceController"> <div class="container-fluid apollo-container hidden" ng-controller="LinkNamespaceController">
<div class="row"> <div class="row">
<div class="col-md-8 col-md-offset-2"> <div class="col-md-8 col-md-offset-2">
...@@ -34,17 +34,14 @@ ...@@ -34,17 +34,14 @@
<div class="panel-body"> <div class="panel-body">
<div class="alert alert-info" role="alert" ng-show="type == 'link'"> <div class="alert alert-info no-radius">
<strong>Tips:</strong> <strong>Tips:</strong>
<ul> <ul ng-show="type == 'link'">
<li>公共namespace所属的应用通过关联公共namespace来配置公共部分的配置</li> <li>公共namespace所属的应用通过关联公共namespace来配置公共部分的配置</li>
<li>其它应用可以通过关联公共namespace来覆盖公共部分的配置</li> <li>其它应用可以通过关联公共namespace来覆盖公共部分的配置</li>
<li>如果其它应用不需要覆盖公共部分的配置,那么无需关联公共namespace</li> <li>如果其它应用不需要覆盖公共部分的配置,那么无需关联公共namespace</li>
</ul> </ul>
</div> <ul ng-show="type == 'create'">
<div class="alert alert-info" ng-show="type == 'create'">
<strong>Tips:</strong>
<ul>
<li> <li>
通过创建一个公共的namespace可以实现公共组件的配置,或多个应用共享同一份配置的需求 通过创建一个公共的namespace可以实现公共组件的配置,或多个应用共享同一份配置的需求
</li> </li>
...@@ -70,13 +67,13 @@ ...@@ -70,13 +67,13 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">应用ID</label> <label class="col-sm-3 control-label">应用ID</label>
<div class="col-sm-6" valdr-form-group> <div class="col-sm-6" valdr-form-group>
<label ng-bind="appId"></label> <label class="form-control-static" ng-bind="appId"></label>
</div> </div>
</div> </div>
<div class="form-horizontal" ng-show="type == 'link'"> <div class="form-horizontal" ng-show="type == 'link'">
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label"> <label class="col-sm-3 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
选择集群</label> 选择集群</label>
<div class="col-sm-6" valdr-form-group> <div class="col-sm-6" valdr-form-group>
<apolloclusterselector apollo-app-id="appId" apollo-default-all-checked="true" <apolloclusterselector apollo-app-id="appId" apollo-default-all-checked="true"
...@@ -86,7 +83,7 @@ ...@@ -86,7 +83,7 @@
</div> </div>
<div class="form-group" ng-show="type == 'create'"> <div class="form-group" ng-show="type == 'create'">
<label class="col-sm-3 control-label"> <label class="col-sm-3 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
名称</label> 名称</label>
<div class="col-sm-4" valdr-form-group> <div class="col-sm-4" valdr-form-group>
<div ng-class="{'input-group':appNamespace.isPublic}"> <div ng-class="{'input-group':appNamespace.isPublic}">
...@@ -108,7 +105,7 @@ ...@@ -108,7 +105,7 @@
</div> </div>
<div class="form-group" ng-show="type == 'create' && hasRootPermission"> <div class="form-group" ng-show="type == 'create' && hasRootPermission">
<label class="col-sm-3 control-label"> <label class="col-sm-3 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
类型</label> 类型</label>
<div class="col-sm-4" valdr-form-group> <div class="col-sm-4" valdr-form-group>
<label class="radio-inline"> <label class="radio-inline">
...@@ -130,7 +127,7 @@ ...@@ -130,7 +127,7 @@
</div> </div>
<div class="form-group" ng-show="type == 'link'"> <div class="form-group" ng-show="type == 'link'">
<label class="col-sm-3 control-label"> <label class="col-sm-3 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
namespace</label> namespace</label>
<div class="col-sm-4" valdr-form-group> <div class="col-sm-4" valdr-form-group>
<select id="namespaces"> <select id="namespaces">
......
...@@ -44,9 +44,9 @@ ...@@ -44,9 +44,9 @@
<button type="submit" class="btn btn-default" style="margin-left: 20px;" ng-disabled="modifyRoleSubmitBtnDisabled">添加</button> <button type="submit" class="btn btn-default" style="margin-left: 20px;" ng-disabled="modifyRoleSubmitBtnDisabled">添加</button>
</form> </form>
<!-- Split button --> <!-- Split button -->
<div class="user-container"> <div class="item-container">
<div class="btn-group user-info" ng-repeat="user in rolesAssignedUsers.modifyRoleUsers"> <div class="btn-group item-info" ng-repeat="user in rolesAssignedUsers.modifyRoleUsers">
<button type="button" class="btn btn-default" ng-bind="user.userId"></button> <button type="button" class="btn btn-default" ng-bind="user.userId"></button>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false" ng-click="removeUserRole('ModifyNamespace', user.userId)"> aria-haspopup="true" aria-expanded="false" ng-click="removeUserRole('ModifyNamespace', user.userId)">
...@@ -74,8 +74,8 @@ ...@@ -74,8 +74,8 @@
<button type="submit" class="btn btn-default" style="margin-left: 20px;" ng-disabled="ReleaseRoleSubmitBtnDisabled">添加</button> <button type="submit" class="btn btn-default" style="margin-left: 20px;" ng-disabled="ReleaseRoleSubmitBtnDisabled">添加</button>
</form> </form>
<!-- Split button --> <!-- Split button -->
<div class="user-container"> <div class="item-container">
<div class="btn-group user-info" ng-repeat="user in rolesAssignedUsers.releaseRoleUsers"> <div class="btn-group item-info" ng-repeat="user in rolesAssignedUsers.releaseRoleUsers">
<button type="button" class="btn btn-default" ng-bind="user.userId"></button> <button type="button" class="btn btn-default" ng-bind="user.userId"></button>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false" ng-click="removeUserRole('ReleaseNamespace', user.userId)"> aria-haspopup="true" aria-expanded="false" ng-click="removeUserRole('ReleaseNamespace', user.userId)">
......
appUtil.service('AppUtil', ['toastr', '$window', function (toastr, $window) { appUtil.service('AppUtil', ['toastr', '$window', function (toastr, $window) {
return { function parseErrorMsg(response) {
errorMsg: function (response) {
if (response.status == -1) { if (response.status == -1) {
return "您的登录信息已过期,请刷新页面后重试"; return "您的登录信息已过期,请刷新页面后重试";
} }
...@@ -10,6 +9,12 @@ appUtil.service('AppUtil', ['toastr', '$window', function (toastr, $window) { ...@@ -10,6 +9,12 @@ appUtil.service('AppUtil', ['toastr', '$window', function (toastr, $window) {
msg += " Msg:" + response.data.message; msg += " Msg:" + response.data.message;
} }
return msg; return msg;
}
return {
errorMsg: parseErrorMsg,
showErrorMsg: function (response, title) {
toastr.error(parseErrorMsg(response), title);
}, },
parseParams: function (query, notJumpToHomePage) { parseParams: function (query, notJumpToHomePage) {
if (!query) { if (!query) {
...@@ -41,6 +46,15 @@ appUtil.service('AppUtil', ['toastr', '$window', function (toastr, $window) { ...@@ -41,6 +46,15 @@ appUtil.service('AppUtil', ['toastr', '$window', function (toastr, $window) {
} }
}); });
return data; return data;
},
showModal: function (modal) {
$(modal).modal("show");
},
hideModal: function (modal) {
$(modal).modal("hide");
},
checkIPV4:function (ip) {
return /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/.test(ip);
} }
} }
}]); }]);
...@@ -14,11 +14,16 @@ $(document).ready(function () { ...@@ -14,11 +14,16 @@ $(document).ready(function () {
// bootstrap tooltip & textarea scroll // bootstrap tooltip & textarea scroll
setInterval(function () { setInterval(function () {
$('[data-tooltip="tooltip"]').tooltip(); $('[data-tooltip="tooltip"]').tooltip({
trigger : 'hover'
$("textarea").niceScroll({styler: "fb", cursorcolor: "#fff"}); });
$("pre").niceScroll({styler: "fb", cursorcolor: "#fff"}); }, 2500);
setTimeout(function () {
$("textarea").niceScroll({cursoropacitymax: 0});
$("pre").niceScroll({cursoropacitymax: 0});
$(".release-history-list").niceScroll({cursoropacitymax: 0});
$(".release-info .config").niceScroll({cursoropacitymax: 0});
}, 2500); }, 2500);
}); });
......
...@@ -16,7 +16,8 @@ cluster_module.controller('ClusterController', ...@@ -16,7 +16,8 @@ cluster_module.controller('ClusterController',
result.forEach(function (env) { result.forEach(function (env) {
$scope.envs.push({name: env, checked: false}); $scope.envs.push({name: env, checked: false});
}) });
$(".apollo-container").removeClass("hidden");
}, function (result) { }, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载环境信息出错"); toastr.error(AppUtil.errorMsg(result), "加载环境信息出错");
}); });
......
...@@ -57,15 +57,19 @@ function IndexController($scope, $window, toastr, AppUtil, AppService, UserServi ...@@ -57,15 +57,19 @@ function IndexController($scope, $window, toastr, AppUtil, AppService, UserServi
$scope.favoritesPage += 1; $scope.favoritesPage += 1;
$scope.hasMoreFavorites = result.length == size; $scope.hasMoreFavorites = result.length == size;
if ($scope.favoritesPage == 1){
$("#app-list").removeClass("hidden");
}
if (!result || result.length == 0) { if (!result || result.length == 0) {
return; return;
} }
var appIds = []; var appIds = [];
result.forEach(function (favorite) { result.forEach(function (favorite) {
appIds.push(favorite.appId); appIds.push(favorite.appId);
}); });
AppService.find_apps(appIds.join(",")) AppService.find_apps(appIds.join(","))
.then(function (apps) { .then(function (apps) {
//sort //sort
...@@ -78,6 +82,7 @@ function IndexController($scope, $window, toastr, AppUtil, AppService, UserServi ...@@ -78,6 +82,7 @@ function IndexController($scope, $window, toastr, AppUtil, AppService, UserServi
app.favoriteId = favorite.id; app.favoriteId = favorite.id;
$scope.favorites.push(app); $scope.favorites.push(app);
}); });
}); });
}) })
} }
......
...@@ -29,6 +29,7 @@ namespace_module.controller("LinkNamespaceController", ...@@ -29,6 +29,7 @@ namespace_module.controller("LinkNamespaceController",
width: '100%', width: '100%',
data: publicNamespaces data: publicNamespaces
}); });
$(".apollo-container").removeClass("hidden");
}, function (result) { }, function (result) {
toastr.error(AppUtil.errorMsg(result), "load public namespace error"); toastr.error(AppUtil.errorMsg(result), "load public namespace error");
}); });
...@@ -110,11 +111,20 @@ namespace_module.controller("LinkNamespaceController", ...@@ -110,11 +111,20 @@ namespace_module.controller("LinkNamespaceController",
}); });
} else { } else {
var namespaceNameLength = $scope.concatNamespace().length;
if (namespaceNameLength > 32){
toastr.error("namespace名称不能大于32个字符. 部门前缀"
+ (namespaceNameLength - $scope.appNamespace.name.length)
+ "个字符, 名称" + $scope.appNamespace.name.length + "个字符"
);
return;
}
$scope.submitBtnDisabled = true; $scope.submitBtnDisabled = true;
NamespaceService.createAppNamespace($scope.appId, $scope.appNamespace).then( NamespaceService.createAppNamespace($scope.appId, $scope.appNamespace).then(
function (result) { function (result) {
$scope.step = 2; $scope.step = 2;
setInterval(function () { setTimeout(function () {
$scope.submitBtnDisabled = false; $scope.submitBtnDisabled = false;
if ($scope.appNamespace.isPublic) { if ($scope.appNamespace.isPublic) {
$window.location.reload(); $window.location.reload();
......
application_module.controller("ConfigBaseInfoController", application_module.controller("ConfigBaseInfoController",
['$rootScope', '$scope', '$location', 'toastr', 'UserService', 'AppService', ['$rootScope', '$scope', '$location', 'toastr', 'EventManager', 'UserService',
'AppService',
'FavoriteService', 'FavoriteService',
'PermissionService', 'PermissionService',
'AppUtil', ConfigBaseInfoController]); 'AppUtil', ConfigBaseInfoController]);
function ConfigBaseInfoController($rootScope, $scope, $location, toastr, UserService, AppService, FavoriteService, function ConfigBaseInfoController($rootScope, $scope, $location, toastr, EventManager, UserService, AppService,
FavoriteService,
PermissionService, PermissionService,
AppUtil) { AppUtil) {
...@@ -27,7 +29,7 @@ function ConfigBaseInfoController($rootScope, $scope, $location, toastr, UserSer ...@@ -27,7 +29,7 @@ function ConfigBaseInfoController($rootScope, $scope, $location, toastr, UserSer
$rootScope.pageContext.userId = result.userId; $rootScope.pageContext.userId = result.userId;
loadAppInfo(); loadAppInfo();
handleFavorite(); handleFavorite();
},function (result) { }, function (result) {
toastr.error(AppUtil.errorMsg(result), "获取用户登录信息失败"); toastr.error(AppUtil.errorMsg(result), "获取用户登录信息失败");
}); });
...@@ -129,7 +131,8 @@ function ConfigBaseInfoController($rootScope, $scope, $location, toastr, UserSer ...@@ -129,7 +131,8 @@ function ConfigBaseInfoController($rootScope, $scope, $location, toastr, UserSer
if (!$rootScope.pageContext.env) { if (!$rootScope.pageContext.env) {
$rootScope.pageContext.env = nodes[0].env; $rootScope.pageContext.env = nodes[0].env;
} }
$rootScope.refreshNamespaces();
EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE);
nodes.forEach(function (env) { nodes.forEach(function (env) {
if (!env.clusters || env.clusters.length == 0) { if (!env.clusters || env.clusters.length == 0) {
...@@ -200,7 +203,7 @@ function ConfigBaseInfoController($rootScope, $scope, $location, toastr, UserSer ...@@ -200,7 +203,7 @@ function ConfigBaseInfoController($rootScope, $scope, $location, toastr, UserSer
cluster: $rootScope.pageContext.clusterName cluster: $rootScope.pageContext.clusterName
})); }));
$rootScope.refreshNamespaces(); EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE);
} }
}); });
......
application_module.controller("ConfigNamespaceController", application_module.controller("ConfigNamespaceController",
['$rootScope', '$scope', '$window', '$location', 'toastr', 'AppUtil', 'ConfigService', ['$rootScope', '$scope', 'toastr', 'AppUtil', 'EventManager', 'ConfigService',
'PermissionService', 'PermissionService', 'UserService', 'NamespaceBranchService',
'CommitService', 'NamespaceLockService', 'UserService', 'ReleaseService', controller]);
function ($rootScope, $scope, $window, $location, toastr, AppUtil, ConfigService,
PermissionService,
CommitService, NamespaceLockService, UserService, ReleaseService) {
var namespace_view_type = {
TEXT: 'text',
TABLE: 'table',
HISTORY: 'history'
};
var TABLE_VIEW_OPER_TYPE = {
CREATE: 'create',
UPDATE: 'update'
};
$rootScope.refreshNamespaces = refreshNamespaces;
$scope.commitChange = commitChange;
$scope.prepareReleaseNamespace = prepareReleaseNamespace;
$scope.release = release;
$scope.switchReleaseChangeViewType = switchReleaseChangeViewType;
$scope.showRollbackAlertDialog = showRollbackAlertDialog;
$scope.preRollback = preRollback; function controller($rootScope, $scope, toastr, AppUtil, EventManager, ConfigService,
PermissionService, UserService, NamespaceBranchService) {
$scope.rollback = rollback; $scope.rollback = rollback;
$scope.preDeleteItem = preDeleteItem; $scope.preDeleteItem = preDeleteItem;
$scope.deleteItem = deleteItem; $scope.deleteItem = deleteItem;
$scope.editItem = editItem; $scope.editItem = editItem;
$scope.createItem = createItem; $scope.createItem = createItem;
$scope.doItem = doItem;
$scope.closeTip = closeTip; $scope.closeTip = closeTip;
$scope.showText = showText; $scope.showText = showText;
$scope.createBranch = createBranch;
$scope.preCreateBranch = preCreateBranch;
$scope.preDeleteBranch = preDeleteBranch;
$scope.deleteBranch = deleteBranch;
$scope.showNoModifyPermissionDialog = showNoModifyPermissionDialog; $scope.showNoModifyPermissionDialog = showNoModifyPermissionDialog;
$scope.lockCheck = lockCheck; $scope.lockCheck = lockCheck;
$scope.releaseBtnDisabled = false;
$scope.rollbackBtnDisabled = false;
$scope.addItemBtnDisabled = false;
$scope.commitChangeBtnDisabled = false;
init(); init();
function init() { function init() {
PermissionService.get_app_role_users($rootScope.pageContext.appId) PermissionService.get_app_role_users($rootScope.pageContext.appId)
.then(function (result) { .then(function (result) {
...@@ -76,14 +41,24 @@ application_module.controller("ConfigNamespaceController", ...@@ -76,14 +41,24 @@ application_module.controller("ConfigNamespaceController",
} }
function refreshNamespaces(viewType) { EventManager.subscribe(EventManager.EventType.REFRESH_NAMESPACE,
function (context) {
if (context.namespace){
refreshSingleNamespace(context.namespace);
}else {
refreshAllNamespaces();
}
});
function refreshAllNamespaces() {
if ($rootScope.pageContext.env == '') { if ($rootScope.pageContext.env == '') {
return; return;
} }
ConfigService.load_all_namespaces($rootScope.pageContext.appId, ConfigService.load_all_namespaces($rootScope.pageContext.appId,
$rootScope.pageContext.env, $rootScope.pageContext.env,
$rootScope.pageContext.clusterName, $rootScope.pageContext.clusterName).then(
viewType).then(
function (result) { function (result) {
$scope.namespaces = result; $scope.namespaces = result;
...@@ -93,138 +68,37 @@ application_module.controller("ConfigNamespaceController", ...@@ -93,138 +68,37 @@ application_module.controller("ConfigNamespaceController",
}); });
} }
function commitChange(namespace) {
var model = {
configText: namespace.editText,
namespaceId: namespace.baseInfo.id,
format: namespace.format
};
//prevent repeat submit
if ($scope.commitChangeBtnDisabled) {
return;
}
$scope.commitChangeBtnDisabled = true;
ConfigService.modify_items($rootScope.pageContext.appId,
$rootScope.pageContext.env,
$rootScope.pageContext.clusterName,
namespace.baseInfo.namespaceName,
model).then(
function (result) {
toastr.success("更新成功, 如需生效请发布");
//refresh all namespace items
$rootScope.refreshNamespaces();
$scope.commitChangeBtnDisabled = false;
return true;
}, function (result) { function refreshSingleNamespace(namespace) {
toastr.error(AppUtil.errorMsg(result), "更新失败"); if ($rootScope.pageContext.env == '') {
$scope.commitChangeBtnDisabled = false;
return false;
}
);
}
var releaseModal = $('#releaseModal');
$scope.toReleaseNamespace = {};
function prepareReleaseNamespace(namespace) {
if (!namespace.hasReleasePermission) {
$('#releaseNoPermissionDialog').modal('show');
return; return;
} else if (namespace.lockOwner && $scope.currentUser == namespace.lockOwner) {
//自己修改不能自己发布
$('#releaseDenyDialog').modal('show');
} else {
$('#releaseModal').modal('show');
}
$scope.releaseTitle = new Date().Format("yyyyMMddhhmmss") + "-release";
$scope.toReleaseNamespace = namespace;
} }
$scope.releaseComment = ''; ConfigService.load_namespace($rootScope.pageContext.appId,
function release() { $rootScope.pageContext.env,
$scope.releaseBtnDisabled = true;
ReleaseService.release($rootScope.pageContext.appId, $rootScope.pageContext.env,
$rootScope.pageContext.clusterName, $rootScope.pageContext.clusterName,
$scope.toReleaseNamespace.baseInfo.namespaceName, namespace.baseInfo.namespaceName).then(
$scope.releaseTitle,
$scope.releaseComment).then(
function (result) { function (result) {
releaseModal.modal('hide');
toastr.success("发布成功");
//refresh all namespace items
$scope.releaseBtnDisabled = false;
$rootScope.refreshNamespaces();
}, function (result) {
$scope.releaseBtnDisabled = false;
toastr.error(AppUtil.errorMsg(result), "发布失败");
$scope.namespaces.forEach(function (namespace, index) {
if (namespace.baseInfo.namespaceName == result.baseInfo.namespaceName){
$scope.namespaces[index] = result;
} }
);
}
$scope.releaseChangeViewType = 'change';
function switchReleaseChangeViewType(type) {
$scope.releaseChangeViewType = type;
}
function showRollbackAlertDialog() {
$("#rollbackModal").modal('hide');
$("#rollbackAlertDialog").modal('show');
}
$scope.toRollbackNamespace = {};
function preRollback(namespace) {
$scope.toRollbackNamespace = namespace;
//load latest two active releases
ReleaseService.findActiveReleases($rootScope.pageContext.appId,
$rootScope.pageContext.env,
$rootScope.pageContext.clusterName,
$scope.toRollbackNamespace.baseInfo.namespaceName,
0, 2)
.then(function (result) {
if (result.length <= 1) {
toastr.error("没有可以回滚的发布历史");
return;
}
$scope.firstRelease = result[0];
$scope.secondRelease = result[1];
ReleaseService.compare($rootScope.pageContext.env,
$scope.firstRelease.id,
$scope.secondRelease.id)
.then(function (result) {
$scope.releaseCompareResult = result.changes;
$("#rollbackModal").modal('show');
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "对比失败");
}) })
}, function (result) { }, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载最近两次发布失败"); toastr.error(AppUtil.errorMsg(result), "加载配置信息出错");
}); });
} }
function rollback() { function rollback() {
$scope.rollbackBtnDisabled = true; EventManager.emit(EventManager.EventType.ROLLBACK_NAMESPACE);
ReleaseService.rollback(
$rootScope.pageContext.env,
$scope.firstRelease.id)
.then(function (result) {
toastr.success("回滚成功");
$scope.rollbackBtnDisabled = false;
$("#rollbackModal").modal("hide");
$rootScope.refreshNamespaces();
}, function (result) {
$scope.rollbackBtnDisabled = false;
toastr.error(AppUtil.errorMsg(result), "回滚失败");
})
} }
$scope.tableViewOperType = '', $scope.item = {}; $scope.tableViewOperType = '', $scope.item = {};
var toOperationNamespace; $scope.toOperationNamespace;
var toDeleteItemId = 0; var toDeleteItemId = 0;
...@@ -233,7 +107,7 @@ application_module.controller("ConfigNamespaceController", ...@@ -233,7 +107,7 @@ application_module.controller("ConfigNamespaceController",
return; return;
} }
toOperationNamespace = namespace; $scope.toOperationNamespace = namespace;
toDeleteItemId = itemId; toDeleteItemId = itemId;
$("#deleteConfirmDialog").modal("show"); $("#deleteConfirmDialog").modal("show");
...@@ -243,119 +117,69 @@ application_module.controller("ConfigNamespaceController", ...@@ -243,119 +117,69 @@ application_module.controller("ConfigNamespaceController",
ConfigService.delete_item($rootScope.pageContext.appId, ConfigService.delete_item($rootScope.pageContext.appId,
$rootScope.pageContext.env, $rootScope.pageContext.env,
$rootScope.pageContext.clusterName, $rootScope.pageContext.clusterName,
toOperationNamespace.baseInfo.namespaceName, $scope.toOperationNamespace.baseInfo.namespaceName,
toDeleteItemId).then( toDeleteItemId).then(
function (result) { function (result) {
toastr.success("删除成功!"); toastr.success("删除成功!");
$rootScope.refreshNamespaces(); EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE,
{
namespace: $scope.toOperationNamespace
});
}, function (result) { }, function (result) {
toastr.error(AppUtil.errorMsg(result), "删除失败"); toastr.error(AppUtil.errorMsg(result), "删除失败");
}); });
} }
//修改配置 //修改配置
function editItem(namespace, item) { function editItem(namespace, toEditItem) {
if (!lockCheck(namespace)) { if (!lockCheck(namespace)) {
return; return;
} }
switchTableViewOperType(TABLE_VIEW_OPER_TYPE.UPDATE);
$scope.item = _.clone(item);
toOperationNamespace = namespace;
$("#itemModal").modal("show"); $scope.item = _.clone(toEditItem);
}
//新增配置 if (namespace.isBranch) {
function createItem(namespace) { var existedItem = false;
if (!lockCheck(namespace)) { namespace.items.forEach(function (item) {
return; //branch items contain the item
if (!item.isDeleted && item.item.key == toEditItem.key) {
existedItem = true;
} }
});
switchTableViewOperType(TABLE_VIEW_OPER_TYPE.CREATE); if (!existedItem) {
$scope.item = {}; $scope.item.lineNum = 0;
toOperationNamespace = namespace; $scope.item.tableViewOperType = 'create';
$('#itemModal').modal('show'); } else {
$scope.item.tableViewOperType = 'update';
} }
var selectedClusters = []; } else {
$scope.collectSelectedClusters = function (data) { $scope.item.tableViewOperType = 'update';
selectedClusters = data;
};
function switchTableViewOperType(type) {
$scope.tableViewOperType = type;
} }
var itemModal = $("#itemModal"); $scope.toOperationNamespace = namespace;
function doItem() {
if (selectedClusters.length == 0) { AppUtil.showModal('#itemModal');
toastr.error("请选择集群");
return;
} }
if (!$scope.item.value) { //新增配置
$scope.item.value = ""; function createItem(namespace) {
} if (!lockCheck(namespace)) {
if ($scope.tableViewOperType == TABLE_VIEW_OPER_TYPE.CREATE) {
//check key unique
var hasRepeatKey = false;
toOperationNamespace.items.forEach(function (item) {
if (!item.isDeleted && $scope.item.key == item.item.key) {
toastr.error("key=" + $scope.item.key + " 已存在");
hasRepeatKey = true;
return;
}
});
if (hasRepeatKey) {
return; return;
} }
$scope.addItemBtnDisabled = true; $scope.item = {
tableViewOperType: 'create'
selectedClusters.forEach(function (cluster) { };
ConfigService.create_item($rootScope.pageContext.appId,
cluster.env,
cluster.name,
toOperationNamespace.baseInfo.namespaceName,
$scope.item).then(
function (result) {
toastr.success(cluster.env + " , " + $scope.item.key, "添加成功");
if (cluster.env == $rootScope.pageContext.env && cluster.name == $rootScope.pageContext.clusterName){
$rootScope.refreshNamespaces(namespace_view_type.TABLE);
}
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "添加失败");
});
});
$scope.addItemBtnDisabled = false;
itemModal.modal('hide');
} else {
if (!$scope.item.comment) {
$scope.item.comment = "";
}
ConfigService.update_item($rootScope.pageContext.appId, $scope.toOperationNamespace = namespace;
$rootScope.pageContext.env, AppUtil.showModal('#itemModal');
$rootScope.pageContext.clusterName,
toOperationNamespace.baseInfo.namespaceName,
$scope.item).then(
function (result) {
toastr.success("更新成功, 如需生效请发布");
itemModal.modal('hide');
$rootScope.refreshNamespaces(namespace_view_type.TABLE);
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "更新失败");
});
} }
} var selectedClusters = [];
$scope.collectSelectedClusters = function (data) {
selectedClusters = data;
};
function lockCheck(namespace) { function lockCheck(namespace) {
if (namespace.lockOwner && $scope.currentUser != namespace.lockOwner) { if (namespace.lockOwner && $scope.currentUser != namespace.lockOwner) {
...@@ -387,17 +211,68 @@ application_module.controller("ConfigNamespaceController", ...@@ -387,17 +211,68 @@ application_module.controller("ConfigNamespaceController",
function showText(text) { function showText(text) {
$scope.text = text; $scope.text = text;
$('#showText').modal('show'); $('#showTextModal').modal('show');
} }
function showNoModifyPermissionDialog() { function showNoModifyPermissionDialog() {
$("#modifyNoPermissionDialog").modal('show'); $("#modifyNoPermissionDialog").modal('show');
} }
var toCreateBranchNamespace = {};
function preCreateBranch(namespace) {
toCreateBranchNamespace = namespace;
AppUtil.showModal("#createBranchTips");
}
function createBranch() {
NamespaceBranchService.createBranch($rootScope.pageContext.appId,
$rootScope.pageContext.env,
$rootScope.pageContext.clusterName,
toCreateBranchNamespace.baseInfo.namespaceName)
.then(function (result) {
toastr.success("创建灰度成功");
EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE,
{
namespace: toCreateBranchNamespace
});
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "创建灰度失败");
})
}
function preDeleteBranch(branch) {
//normal delete
branch.branchStatus = 0;
$scope.toDeleteBranch = branch;
AppUtil.showModal('#deleteBranchDialog');
}
function deleteBranch() {
NamespaceBranchService.deleteBranch($rootScope.pageContext.appId,
$rootScope.pageContext.env,
$rootScope.pageContext.clusterName,
$scope.toDeleteBranch.baseInfo.namespaceName,
$scope.toDeleteBranch.baseInfo.clusterName
)
.then(function (result) {
toastr.success("删除成功");
EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE,
{
namespace: $scope.toDeleteBranch.parentNamespace
});
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "删除分支失败");
})
}
$('.config-item-container').removeClass('hide'); $('.config-item-container').removeClass('hide');
new Clipboard('.clipboard'); new Clipboard('.clipboard');
} }
]);
release_history_module.controller("ReleaseHistoryController", release_history_module.controller("ReleaseHistoryController",
['$scope', '$location', '$anchorScroll', '$window', 'toastr', 'AppService', 'AppUtil', ['$scope', '$location', 'AppUtil',
'ReleaseService', 'ReleaseService', 'ConfigService', 'ReleaseHistoryService', releaseHistoryController
function ($scope, $location, $anchorScroll, $window, toastr, AppService, AppUtil, ReleaseService) { ]);
function releaseHistoryController($scope, $location, AppUtil,
ReleaseService, ConfigService, ReleaseHistoryService) {
var params = AppUtil.parseParams($location.$$url); var params = AppUtil.parseParams($location.$$url);
$scope.pageContext = { $scope.pageContext = {
...@@ -9,63 +12,126 @@ release_history_module.controller("ReleaseHistoryController", ...@@ -9,63 +12,126 @@ release_history_module.controller("ReleaseHistoryController",
env: params.env, env: params.env,
clusterName: params.clusterName, clusterName: params.clusterName,
namespaceName: params.namespaceName, namespaceName: params.namespaceName,
scrollTo: params.scrollTo releaseId: params.releaseId
};
var PAGE_SIZE = 10;
var CONFIG_VIEW_TYPE = {
DIFF: 'diff',
ALL: 'all'
}; };
$scope.page = 0; $scope.page = 0;
$scope.releases = []; $scope.releaseHistories = [];
$scope.hasLoadAll = false; $scope.hasLoadAll = false;
$scope.selectedReleaseHistory = 0;
$scope.isTextNamespace = false;
$scope.showReleaseHistoryDetail = showReleaseHistoryDetail;
$scope.switchConfigViewType = switchConfigViewType;
$scope.findReleaseHistory = findReleaseHistory;
$scope.showText = showText;
$scope.findReleases = findReleases; init();
$scope.loadMore = loadMore; function init() {
findReleases($scope.page); findReleaseHistory();
loadNamespace();
}
var hasFindActiveRelease = false; function findReleaseHistory() {
function findReleases(page) { if ($scope.hasLoadAll) {
var size = 10; return;
ReleaseService.findAllRelease($scope.pageContext.appId, }
ReleaseHistoryService.findReleaseHistoryByNamespace($scope.pageContext.appId,
$scope.pageContext.env, $scope.pageContext.env,
$scope.pageContext.clusterName, $scope.pageContext.clusterName,
$scope.pageContext.namespaceName, $scope.pageContext.namespaceName,
page, $scope.page, PAGE_SIZE)
size)
.then(function (result) { .then(function (result) {
if (!result || result.length < size) { if (!result || result.length < PAGE_SIZE) {
$scope.hasLoadAll = true; $scope.hasLoadAll = true;
} }
var hasParseNamespaceType = false; if (result.length == 0) {
return;
result.forEach(function (release) {
if (!hasParseNamespaceType) {
$scope.isTextFile =
/\.(json|yaml|yml|xml)$/gi.test(
release.baseInfo.namespaceName);
hasParseNamespaceType = true;
} }
if (!hasFindActiveRelease && !release.baseInfo.isAbandoned) {
release.active = true; $scope.releaseHistories = $scope.releaseHistories.concat(result);
hasFindActiveRelease = true;
if ($scope.page == 0) {
var defaultToShowReleaseHistory = result[0];
if ($scope.pageContext.releaseId){
$scope.releaseHistories.forEach(function (history) {
if ($scope.pageContext.releaseId == history.releaseId){
defaultToShowReleaseHistory = history;
} }
$scope.releases.push(release);
}) })
}
showReleaseHistoryDetail(defaultToShowReleaseHistory);
}
$scope.page = $scope.page + 1;
if ($scope.pageContext.scrollTo){ if ($scope.page == 1){
$location.hash($scope.pageContext.scrollTo); $(".release-history").removeClass('hidden');
$anchorScroll();
} }
}, function (result) { }, function (result) {
toastr.error(AppUtil.errorMsg(result)); AppUtil.showErrorMsg(result, "加载发布历史信息出错");
}); });
} }
function loadMore() { function loadNamespace() {
$scope.page += 1; ConfigService.load_namespace($scope.pageContext.appId,
findReleases($scope.page); $scope.pageContext.env,
$scope.pageContext.clusterName,
$scope.pageContext.namespaceName)
.then(function (result) {
$scope.isTextNamespace = result.format != "properties";
})
}
function showReleaseHistoryDetail(history) {
$scope.history = history;
$scope.selectedReleaseHistory = history.id;
history.viewType = CONFIG_VIEW_TYPE.DIFF;
showReleaseDiffConfiguration(history);
}
function switchConfigViewType(history, viewType) {
history.viewType = viewType;
if (viewType == CONFIG_VIEW_TYPE.DIFF) {
showReleaseDiffConfiguration(history);
} }
}]); }
function showReleaseDiffConfiguration(history) {
history.viewType = CONFIG_VIEW_TYPE.DIFF;
if (!history.changes) {
//Set previous release id to master latest release id when branch first gray release.
if (history.operation == 2 && history.previousReleaseId == 0){
history.previousReleaseId = history.operationContext.baseReleaseId;
}
ReleaseService.compare($scope.pageContext.env,
history.previousReleaseId,
history.releaseId)
.then(function (result) {
history.changes = result.changes;
})
}
}
function showText(text) {
$scope.text = text;
AppUtil.showModal("#showTextModal");
}
}
...@@ -48,7 +48,7 @@ sync_item_module.controller("SyncItemController", ...@@ -48,7 +48,7 @@ sync_item_module.controller("SyncItemController",
}); });
$scope.viewItems = sourceItems; $scope.viewItems = sourceItems;
$(".apollo-container").removeClass("hidden");
}, function (result) { }, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载配置出错"); toastr.error(AppUtil.errorMsg(result), "加载配置出错");
}); });
...@@ -226,11 +226,9 @@ sync_item_module.controller("SyncItemController", ...@@ -226,11 +226,9 @@ sync_item_module.controller("SyncItemController",
function showText(text) { function showText(text) {
$scope.text = text; $scope.text = text;
$('#showText').modal('show'); AppUtil.showModal('#showTextModal');
} }
new Clipboard('.clipboard');
}]); }]);
/** navbar */ /** navbar */
directive_module.directive('apollonav', function ($compile, $window, toastr, AppUtil, AppService, EnvService, UserService) { directive_module.directive('apollonav',
function ($compile, $window, toastr, AppUtil, AppService, EnvService, UserService) {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: '../../views/common/nav.html', templateUrl: '../../views/common/nav.html',
...@@ -46,7 +47,7 @@ directive_module.directive('apollonav', function ($compile, $window, toastr, App ...@@ -46,7 +47,7 @@ directive_module.directive('apollonav', function ($compile, $window, toastr, App
if ($window.location.href.indexOf("config.html") > -1) { if ($window.location.href.indexOf("config.html") > -1) {
$window.location.hash = "appid=" + selectedApp.appId; $window.location.hash = "appid=" + selectedApp.appId;
$window.location.reload(); $window.location.reload();
}else { } else {
$window.location.href = '/config.html?#appid=' + selectedApp.appId; $window.location.href = '/config.html?#appid=' + selectedApp.appId;
} }
} }
...@@ -115,7 +116,7 @@ directive_module.directive('apollonav', function ($compile, $window, toastr, App ...@@ -115,7 +116,7 @@ directive_module.directive('apollonav', function ($compile, $window, toastr, App
} }
} }
}); });
/** env cluster selector*/ /** env cluster selector*/
directive_module.directive('apolloclusterselector', function ($compile, $window, AppService, AppUtil, toastr) { directive_module.directive('apolloclusterselector', function ($compile, $window, AppService, AppUtil, toastr) {
...@@ -130,7 +131,7 @@ directive_module.directive('apolloclusterselector', function ($compile, $window, ...@@ -130,7 +131,7 @@ directive_module.directive('apolloclusterselector', function ($compile, $window,
select: '=apolloSelect', select: '=apolloSelect',
defaultCheckedEnv: '=apolloDefaultCheckedEnv', defaultCheckedEnv: '=apolloDefaultCheckedEnv',
defaultCheckedCluster: '=apolloDefaultCheckedCluster', defaultCheckedCluster: '=apolloDefaultCheckedCluster',
notCheckedEnv:'=apolloNotCheckedEnv', notCheckedEnv: '=apolloNotCheckedEnv',
notCheckedCluster: '=apolloNotCheckedCluster' notCheckedCluster: '=apolloNotCheckedCluster'
}, },
link: function (scope, element, attrs) { link: function (scope, element, attrs) {
...@@ -140,7 +141,6 @@ directive_module.directive('apolloclusterselector', function ($compile, $window, ...@@ -140,7 +141,6 @@ directive_module.directive('apolloclusterselector', function ($compile, $window,
refreshClusterList(); refreshClusterList();
////// load env //////
function refreshClusterList() { function refreshClusterList() {
AppService.load_nav_tree(scope.appId).then(function (result) { AppService.load_nav_tree(scope.appId).then(function (result) {
scope.clusters = []; scope.clusters = [];
...@@ -154,7 +154,7 @@ directive_module.directive('apolloclusterselector', function ($compile, $window, ...@@ -154,7 +154,7 @@ directive_module.directive('apolloclusterselector', function ($compile, $window,
(cluster.env == scope.defaultCheckedEnv && cluster.name (cluster.env == scope.defaultCheckedEnv && cluster.name
== scope.defaultCheckedCluster); == scope.defaultCheckedCluster);
//not checked //not checked
if (cluster.env == scope.notCheckedEnv && cluster.name == scope.notCheckedCluster){ if (cluster.env == scope.notCheckedEnv && cluster.name == scope.notCheckedCluster) {
cluster.checked = false; cluster.checked = false;
} }
...@@ -162,12 +162,9 @@ directive_module.directive('apolloclusterselector', function ($compile, $window, ...@@ -162,12 +162,9 @@ directive_module.directive('apolloclusterselector', function ($compile, $window,
}) })
}); });
scope.select(collectSelectedClusters()); scope.select(collectSelectedClusters());
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载环境信息出错");
}); });
} }
scope.envAllSelected = scope.defaultAllChecked; scope.envAllSelected = scope.defaultAllChecked;
scope.toggleEnvsCheckedStatus = function () { scope.toggleEnvsCheckedStatus = function () {
...@@ -206,10 +203,10 @@ directive_module.directive('apolloclusterselector', function ($compile, $window, ...@@ -206,10 +203,10 @@ directive_module.directive('apolloclusterselector', function ($compile, $window,
}); });
/** 必填项*/ /** 必填项*/
directive_module.directive('apollorequiredfiled', function ($compile, $window) { directive_module.directive('apollorequiredfield', function ($compile, $window) {
return { return {
restrict: 'E', restrict: 'E',
template: '<span style="color: red">*</span>', template: '<strong style="color: red">*</strong>',
transclude: true, transclude: true,
replace: true replace: true
} }
...@@ -227,12 +224,13 @@ directive_module.directive('apolloconfirmdialog', function ($compile, $window) { ...@@ -227,12 +224,13 @@ directive_module.directive('apolloconfirmdialog', function ($compile, $window) {
title: '=apolloTitle', title: '=apolloTitle',
detail: '=apolloDetail', detail: '=apolloDetail',
showCancelBtn: '=apolloShowCancelBtn', showCancelBtn: '=apolloShowCancelBtn',
doConfirm: '=apolloConfirm' doConfirm: '=apolloConfirm',
cancel:'='
}, },
link: function (scope, element, attrs) { link: function (scope, element, attrs) {
scope.confirm = function () { scope.confirm = function () {
if (scope.doConfirm){ if (scope.doConfirm) {
scope.doConfirm(); scope.doConfirm();
} }
} }
...@@ -258,7 +256,6 @@ directive_module.directive('apolloentrance', function ($compile, $window) { ...@@ -258,7 +256,6 @@ directive_module.directive('apolloentrance', function ($compile, $window) {
} }
}); });
/** entrance */ /** entrance */
directive_module.directive('apollouserselector', function ($compile, $window) { directive_module.directive('apollouserselector', function ($compile, $window) {
return { return {
...@@ -280,7 +277,7 @@ directive_module.directive('apollouserselector', function ($compile, $window) { ...@@ -280,7 +277,7 @@ directive_module.directive('apollouserselector', function ($compile, $window) {
delay: 250, delay: 250,
data: function (params) { data: function (params) {
return { return {
keyword: params.term ? params.term: '', keyword: params.term ? params.term : '',
limit: 100 limit: 100
} }
}, },
...@@ -302,7 +299,7 @@ directive_module.directive('apollouserselector', function ($compile, $window) { ...@@ -302,7 +299,7 @@ directive_module.directive('apollouserselector', function ($compile, $window) {
} }
}; };
function initSelect2(){ function initSelect2() {
$('.' + scope.id).select2(searchUsersAjax); $('.' + scope.id).select2(searchUsersAjax);
} }
......
directive_module.directive('rulesmodal', rulesModalDirective);
function rulesModalDirective(toastr, AppUtil, EventManager, InstanceService) {
return {
restrict: 'E',
templateUrl: '../../views/component/gray-release-rules-modal.html',
transclude: true,
replace: true,
scope: {
appId: '=',
env: '=',
cluster: '='
},
link: function (scope) {
scope.completeEditBtnDisable = false;
scope.batchAddIPs = batchAddIPs;
scope.addRules = addRules;
scope.removeRule = removeRule;
scope.completeEditItem = completeEditItem;
scope.cancelEditItem = cancelEditItem;
scope.initSelectIps = initSelectIps;
EventManager.subscribe(EventManager.EventType.EDIT_GRAY_RELEASE_RULES,
function (context) {
var branch = context.branch;
scope.branch = branch;
if (branch.editingRuleItem.clientIpList && branch.editingRuleItem.clientIpList[0] == '*'){
branch.editingRuleItem.ApplyToAllInstances = true;
}else {
branch.editingRuleItem.ApplyToAllInstances = false;
}
$('.rules-ip-selector').select2({
placeholder: "从实例列表中选择",
allowClear: true
});
AppUtil.showModal('#rulesModal');
});
$('.rules-ip-selector').on('select2:select', function () {
addRules(scope.branch);
});
function addRules(branch) {
var newRules, selector = $('.rules-ip-selector');
newRules = selector.select2('data');
var parsedIPs = [];
newRules.forEach(function (rule) {
parsedIPs.push(rule.text);
});
selector.select2("val", "");
addRuleItemIP(branch, parsedIPs);
scope.$apply();
}
function batchAddIPs(branch, newIPs) {
if (!newIPs) {
return;
}
addRuleItemIP(branch, newIPs.split(','));
}
function addRuleItemIP(branch, newIps) {
var oldIPs = branch.editingRuleItem.draftIpList;
if (newIps && newIps.length > 0) {
newIps.forEach(function (IP) {
if (!AppUtil.checkIPV4(IP)) {
toastr.error("不合法的IP地址:" + IP);
} else if (oldIPs.indexOf(IP) < 0) {
oldIPs.push(IP);
}
})
}
//remove IP:all
oldIPs.forEach(function (IP, index) {
if (IP == "*") {
oldIPs.splice(index, 1);
}
});
}
function removeRule(ruleItem, IP) {
ruleItem.draftIpList.forEach(function (existedRule, index) {
if (existedRule == IP) {
ruleItem.draftIpList.splice(index, 1);
}
})
}
function completeEditItem(branch) {
scope.completeEditBtnDisable = true;
if (!branch.editingRuleItem.clientAppId) {
toastr.error("灰度的AppId不能为空");
scope.completeEditBtnDisable = false;
return;
}
if (branch.editingRuleItem.isNew && branch.rules && branch.rules.ruleItems) {
var errorRuleItem = false;
branch.rules.ruleItems.forEach(function (ruleItem) {
if (ruleItem.clientAppId == branch.editingRuleItem.clientAppId) {
toastr.error("已经存在AppId=" + branch.editingRuleItem.clientAppId + "的规则");
errorRuleItem = true;
}
});
if (errorRuleItem) {
scope.completeEditBtnDisable = false;
return;
}
}
if (!branch.editingRuleItem.ApplyToAllInstances) {
if (branch.editingRuleItem.draftIpList.length == 0) {
toastr.error("IP列表不能为空");
scope.completeEditBtnDisable = false;
return;
} else {
branch.editingRuleItem.clientIpList = branch.editingRuleItem.draftIpList;
}
} else {
branch.editingRuleItem.clientIpList = ['*'];
}
if (!branch.rules) {
branch.rules = {
appId: scope.appId,
clusterName: scope.cluster,
namespaceName: branch.baseInfo.namespaceName,
branchName: branch.baseInfo.clusterName
};
}
if (!branch.rules.ruleItems) {
branch.rules.ruleItems = [];
}
if (branch.editingRuleItem.isNew) {
branch.rules.ruleItems.push(branch.editingRuleItem);
}
branch.editingRuleItem = undefined;
scope.toAddIPs = '';
AppUtil.hideModal('#rulesModal');
EventManager.emit(EventManager.EventType.UPDATE_GRAY_RELEASE_RULES,
{
branch: branch
}, branch.baseInfo.namespaceName);
scope.completeEditBtnDisable = false;
}
function cancelEditItem(branch) {
branch.editingRuleItem.isEdit = false;
branch.editingRuleItem = undefined;
scope.toAddIPs = '';
AppUtil.hideModal('#rulesModal');
}
$('#rulesModal').on('shown.bs.modal', function (e) {
initSelectIps();
});
function initSelectIps() {
scope.selectIps = [];
if (!scope.branch.parentNamespace.isPublic ||
scope.branch.parentNamespace.isLinkedNamespace) {
scope.branch.editingRuleItem.clientAppId = scope.branch.baseInfo.appId;
}
if (!scope.branch.editingRuleItem.clientAppId) {
return;
}
InstanceService.findInstancesByNamespace(scope.appId,
scope.env,
scope.cluster,
scope.branch.baseInfo.namespaceName,
scope.branch.editingRuleItem.clientAppId,
0,
2000)
.then(function (result) {
scope.selectIps = result.content;
});
}
}
}
}
directive_module.directive('itemmodal', itemModalDirective);
function itemModalDirective(toastr, AppUtil, EventManager, ConfigService) {
return {
restrict: 'E',
templateUrl: '../../views/component/item-modal.html',
transclude: true,
replace: true,
scope: {
appId: '=',
env: '=',
cluster: '=',
toOperationNamespace: '=',
item: '='
},
link: function (scope) {
var TABLE_VIEW_OPER_TYPE = {
CREATE: 'create',
UPDATE: 'update'
};
scope.doItem = doItem;
scope.collectSelectedClusters = collectSelectedClusters;
function doItem() {
if (selectedClusters.length == 0) {
toastr.error("请选择集群");
return;
}
if (!scope.item.value) {
scope.item.value = "";
}
if (scope.item.tableViewOperType == TABLE_VIEW_OPER_TYPE.CREATE) {
//check key unique
var hasRepeatKey = false;
scope.toOperationNamespace.items.forEach(function (item) {
if (!item.isDeleted && scope.item.key == item.item.key) {
toastr.error("key=" + scope.item.key + " 已存在");
hasRepeatKey = true;
}
});
if (hasRepeatKey) {
return;
}
scope.item.addItemBtnDisabled = true;
if (scope.toOperationNamespace.isBranch) {
ConfigService.create_item(scope.appId,
scope.env,
scope.toOperationNamespace.baseInfo.clusterName,
scope.toOperationNamespace.baseInfo.namespaceName,
scope.item).then(
function (result) {
EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE,
{
namespace: scope.toOperationNamespace
});
toastr.success("添加成功,如需生效请发布");
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "添加失败");
});
} else {
selectedClusters.forEach(function (cluster) {
ConfigService.create_item(scope.appId,
cluster.env,
cluster.name,
scope.toOperationNamespace.baseInfo.namespaceName,
scope.item).then(
function (result) {
toastr.success(cluster.env + " , " + scope.item.key, "添加成功,如需生效请发布");
if (cluster.env == scope.env &&
cluster.name == scope.cluster) {
EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE,
{
namespace: scope.toOperationNamespace
});
}
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "添加失败");
});
});
}
scope.item.addItemBtnDisabled = false;
AppUtil.hideModal('#itemModal');
} else {
if (!scope.item.comment) {
scope.item.comment = "";
}
ConfigService.update_item(scope.appId,
scope.env,
scope.toOperationNamespace.baseInfo.clusterName,
scope.toOperationNamespace.baseInfo.namespaceName,
scope.item).then(
function (result) {
EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE,
{
namespace: scope.toOperationNamespace
});
AppUtil.hideModal('#itemModal');
toastr.success("更新成功, 如需生效请发布");
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "更新失败");
});
}
}
var selectedClusters = [];
function collectSelectedClusters(data) {
selectedClusters = data;
}
}
}
}
directive_module.directive('mergeandpublishmodal', mergeAndPublishDirective);
function mergeAndPublishDirective(AppUtil, EventManager) {
return {
restrict: 'E',
templateUrl: '../../views/component/merge-and-publish-modal.html',
transclude: true,
replace: true,
scope: {
appId: '=',
env: '=',
cluster: '='
},
link: function (scope) {
scope.showReleaseModal = showReleaseModal;
EventManager.subscribe(EventManager.EventType.MERGE_AND_PUBLISH_NAMESPACE,
function (context) {
var branch = context.branch;
scope.toReleaseNamespace = branch;
scope.toDeleteBranch = branch;
var branchStatusMerge = 2;
branch.branchStatus = branchStatusMerge;
branch.mergeAndPublish = true;
AppUtil.showModal('#mergeAndPublishModal');
});
function showReleaseModal() {
EventManager.emit(EventManager.EventType.PUBLISH_NAMESPACE, {namespace: scope.toReleaseNamespace});
}
}
}
}
directive_module.directive('apollonspanel', directive_module.directive('apollonspanel', directive);
function ($compile, $window, toastr, AppUtil, PermissionService, NamespaceLockService,
UserService, CommitService, ReleaseService, InstanceService) { function directive($window, toastr, AppUtil, EventManager, PermissionService, NamespaceLockService,
UserService, CommitService, ReleaseService, InstanceService, NamespaceBranchService, ConfigService) {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: '../../views/component/namespace-panel.html', templateUrl: '../../views/component/namespace-panel.html',
...@@ -11,24 +12,26 @@ directive_module.directive('apollonspanel', ...@@ -11,24 +12,26 @@ directive_module.directive('apollonspanel',
appId: '=', appId: '=',
env: '=', env: '=',
cluster: '=', cluster: '=',
user: '=',
lockCheck: '=', lockCheck: '=',
preReleaseNs: '=',
preRollback: '=',
createItem: '=', createItem: '=',
editItem: '=', editItem: '=',
preDeleteItem: '=', preDeleteItem: '=',
commitChange: '=',
showText: '=', showText: '=',
showNoModifyPermissionDialog: '=' showNoModifyPermissionDialog: '=',
preCreateBranch: '=',
preDeleteBranch: '=',
showMergeAndPublishGrayTips: '='
}, },
link: function (scope, element, attrs) { link: function (scope) {
//constants //constants
var namespace_view_type = { var namespace_view_type = {
TEXT: 'text', TEXT: 'text',
TABLE: 'table', TABLE: 'table',
HISTORY: 'history', HISTORY: 'history',
INSTANCE: 'instance' INSTANCE: 'instance',
RULE: 'rule'
}; };
var namespace_instance_view_type = { var namespace_instance_view_type = {
...@@ -39,34 +42,51 @@ directive_module.directive('apollonspanel', ...@@ -39,34 +42,51 @@ directive_module.directive('apollonspanel',
var MIN_ROW_SIZE = 10; var MIN_ROW_SIZE = 10;
scope.switchView = switchView; var operate_branch_storage_key = 'OperateBranch';
scope.switchView = switchView;
scope.toggleItemSearchInput = toggleItemSearchInput; scope.toggleItemSearchInput = toggleItemSearchInput;
scope.searchItems = searchItems; scope.searchItems = searchItems;
scope.loadCommitHistory = loadCommitHistory; scope.loadCommitHistory = loadCommitHistory;
scope.toggleTextEditStatus = toggleTextEditStatus; scope.toggleTextEditStatus = toggleTextEditStatus;
scope.goToSyncPage = goToSyncPage; scope.goToSyncPage = goToSyncPage;
scope.modifyByText = modifyByText; scope.modifyByText = modifyByText;
scope.goToParentAppConfigPage = goToParentAppConfigPage; scope.goToParentAppConfigPage = goToParentAppConfigPage;
scope.switchInstanceViewType = switchInstanceViewType; scope.switchInstanceViewType = switchInstanceViewType;
scope.switchBranch = switchBranch;
scope.loadInstanceInfo = loadInstanceInfo; scope.loadInstanceInfo = loadInstanceInfo;
scope.refreshInstancesInfo = refreshInstancesInfo; scope.refreshInstancesInfo = refreshInstancesInfo;
scope.deleteRuleItem = deleteRuleItem;
scope.rollback = rollback;
scope.publish = publish;
scope.mergeAndPublish = mergeAndPublish;
scope.addRuleItem = addRuleItem;
scope.editRuleItem = editRuleItem;
var subscriberId = EventManager.subscribe(EventManager.EventType.UPDATE_GRAY_RELEASE_RULES,
function (context) {
useRules(context.branch);
}, scope.namespace.baseInfo.namespaceName);
scope.$on('$destroy', function () {
EventManager.unsubscribe(EventManager.EventType.UPDATE_GRAY_RELEASE_RULES,
subscriberId, scope.namespace.baseInfo.namespaceName);
});
initNamespace(scope.namespace); init();
//init method function init() {
initNamespace(scope.namespace);
initOther();
}
function initNamespace(namespace, viewType) { function initNamespace(namespace, viewType) {
namespace.hasBranch = false;
namespace.currentOperateBranch = 'master';
namespace.isBranch = false;
namespace.isLinkedNamespace =
namespace.isPublic ? namespace.parentAppId != namespace.baseInfo.appId : false;
namespace.showSearchInput = false; namespace.showSearchInput = false;
namespace.viewItems = namespace.items; namespace.viewItems = namespace.items;
namespace.isPropertiesFormat = namespace.format == 'properties'; namespace.isPropertiesFormat = namespace.format == 'properties';
...@@ -75,45 +95,154 @@ directive_module.directive('apollonspanel', ...@@ -75,45 +95,154 @@ directive_module.directive('apollonspanel',
namespace.latestReleaseInstancesPage = 0; namespace.latestReleaseInstancesPage = 0;
namespace.allInstances = []; namespace.allInstances = [];
namespace.allInstancesPage = 0; namespace.allInstancesPage = 0;
namespace.commitChangeBtnDisabled = false;
//namespace view name hide suffix initNamespaceBranch(namespace);
namespace.viewName = initNamespaceViewName(namespace);
namespace.baseInfo.namespaceName.replace(".xml", "").replace( initNamespaceLock(namespace);
".properties", ""); initNamespaceInstancesCount(namespace);
initPermission(namespace);
if (!viewType) { function initNamespaceBranch(namespace) {
if (namespace.isPropertiesFormat) { NamespaceBranchService.findNamespaceBranch(scope.appId, scope.env,
switchView(namespace, namespace_view_type.TABLE); namespace.baseInfo.clusterName,
namespace.baseInfo.namespaceName)
.then(function (result) {
if (!result.baseInfo) {
return;
}
//namespace has branch
namespace.hasBranch = true;
namespace.branchName = result.baseInfo.clusterName;
//init branch
namespace.branch = result;
namespace.branch.isBranch = true;
namespace.branch.parentNamespace = namespace;
namespace.branch.viewType = namespace_view_type.TABLE;
namespace.branch.isPropertiesFormat = namespace.format == 'properties';
namespace.branch.allInstances = [];//master namespace all instances
namespace.branch.latestReleaseInstances = [];
namespace.branch.latestReleaseInstancesPage = 0;
namespace.branch.instanceViewType = namespace_instance_view_type.LATEST_RELEASE;
namespace.branch.hasLoadInstances = false;
initBranchItems(namespace.branch);
initRules(namespace.branch);
loadInstanceInfo(namespace.branch);
initNamespaceLock(namespace.branch);
initPermission(namespace);
initUserOperateBranchScene(namespace);
});
function initBranchItems(branch) {
branch.masterItems = [];
branch.branchItems = [];
var masterItemsMap = {};
branch.parentNamespace.items.forEach(function (item) {
if (item.item.key) {
masterItemsMap[item.item.key] = item;
}
});
var branchItemsMap = {};
var itemModifiedCnt = 0;
branch.items.forEach(function (item) {
var key = item.item.key;
var masterItem = masterItemsMap[key];
//modify master item and set item's masterReleaseValue
if (masterItem) {
if (masterItem.isModified && masterItem.oldValue) {
item.masterReleaseValue = masterItem.oldValue;
} else if (masterItem.item.value) {
item.masterReleaseValue = masterItem.item.value;
}
} else {//delete branch item
item.masterReleaseValue = '';
}
//delete master item. ignore
if (item.isDeleted && masterItem) {
if (item.masterReleaseValue != item.oldValue) {
itemModifiedCnt++;
branch.branchItems.push(item);
}
} else {//branch's item
branchItemsMap[key] = item;
if (item.isModified) {
itemModifiedCnt++;
}
branch.branchItems.push(item);
}
});
branch.itemModifiedCnt = itemModifiedCnt;
branch.parentNamespace.items.forEach(function (item) {
if (item.item.key) {
if (!branchItemsMap[item.item.key]) {
branch.masterItems.push(item);
} else { } else {
switchView(namespace, namespace_view_type.TEXT); item.hasBranchValue = true;
}
}
})
} }
} else if (viewType == namespace_view_type.TABLE) {
namespace.viewType = namespace_view_type.TABLE;
} }
//permission function initPermission(namespace) {
PermissionService.has_modify_namespace_permission( PermissionService.has_modify_namespace_permission(
scope.appId, scope.appId,
namespace.baseInfo.namespaceName) namespace.baseInfo.namespaceName)
.then(function (result) { .then(function (result) {
//branch has same permission
namespace.hasModifyPermission = result.hasPermission; namespace.hasModifyPermission = result.hasPermission;
}, function (result) { if (namespace.branch) {
namespace.branch.hasModifyPermission = result.hasPermission;
}
}); });
PermissionService.has_release_namespace_permission( PermissionService.has_release_namespace_permission(
scope.appId, scope.appId,
namespace.baseInfo.namespaceName) namespace.baseInfo.namespaceName)
.then(function (result) { .then(function (result) {
//branch has same permission
namespace.hasReleasePermission = result.hasPermission; namespace.hasReleasePermission = result.hasPermission;
}, function (result) { if (namespace.branch) {
namespace.branch.hasReleasePermission = result.hasPermission;
}
}); });
}
function initNamespaceViewName(namespace) {
//namespace view name hide suffix
namespace.viewName =
namespace.baseInfo.namespaceName.replace(".xml", "").replace(
".properties", "");
//lock if (!viewType) {
if (namespace.isPropertiesFormat) {
switchView(namespace, namespace_view_type.TABLE);
} else {
switchView(namespace, namespace_view_type.TEXT);
}
} else if (viewType == namespace_view_type.TABLE) {
namespace.viewType = namespace_view_type.TABLE;
}
}
function initNamespaceLock(namespace) {
NamespaceLockService.get_namespace_lock( NamespaceLockService.get_namespace_lock(
scope.appId, scope.env, scope.appId, scope.env,
scope.cluster, namespace.baseInfo.clusterName,
namespace.baseInfo.namespaceName) namespace.baseInfo.namespaceName)
.then(function (result) { .then(function (result) {
if (result.dataChangeCreatedBy) { if (result.dataChangeCreatedBy) {
...@@ -123,11 +252,28 @@ directive_module.directive('apollonspanel', ...@@ -123,11 +252,28 @@ directive_module.directive('apollonspanel',
} }
}); });
//instance
getInstanceCountByNamespace(namespace);
} }
function getInstanceCountByNamespace(namespace) { function initUserOperateBranchScene(namespace) {
var operateBranchStorage = JSON.parse(localStorage.getItem(operate_branch_storage_key));
var namespaceId = [scope.appId, scope.env, scope.cluster, namespace.baseInfo.namespaceName].join(
"+");
if (!operateBranchStorage) {
operateBranchStorage = {};
}
if (!operateBranchStorage[namespaceId]) {
operateBranchStorage[namespaceId] = namespace.branchName;
}
localStorage.setItem(operate_branch_storage_key, JSON.stringify(operateBranchStorage));
switchBranch(operateBranchStorage[namespaceId]);
}
}
function initNamespaceInstancesCount(namespace) {
InstanceService.getInstanceCountByNamespace(scope.appId, InstanceService.getInstanceCountByNamespace(scope.appId,
scope.env, scope.env,
scope.cluster, scope.cluster,
...@@ -137,6 +283,8 @@ directive_module.directive('apollonspanel', ...@@ -137,6 +283,8 @@ directive_module.directive('apollonspanel',
}) })
} }
function initOther() {
UserService.load_user().then(function (result) { UserService.load_user().then(function (result) {
scope.currentUser = result.userId; scope.currentUser = result.userId;
}); });
...@@ -147,8 +295,26 @@ directive_module.directive('apollonspanel', ...@@ -147,8 +295,26 @@ directive_module.directive('apollonspanel',
}, function (result) { }, function (result) {
}); });
}
function switchBranch(branchName) {
if (branchName != 'master') {
initRules(scope.namespace.branch);
}
scope.namespace.currentOperateBranch = branchName;
//save to local storage
var operateBranchStorage = JSON.parse(localStorage.getItem(operate_branch_storage_key));
if (!operateBranchStorage) {
return;
}
var namespaceId = [scope.appId, scope.env, scope.cluster, scope.namespace.baseInfo.namespaceName].join(
"+");
operateBranchStorage[namespaceId] = branchName;
localStorage.setItem(operate_branch_storage_key, JSON.stringify(operateBranchStorage));
}
//controller method
function switchView(namespace, viewType) { function switchView(namespace, viewType) {
namespace.viewType = viewType; namespace.viewType = viewType;
if (namespace_view_type.TEXT == viewType) { if (namespace_view_type.TEXT == viewType) {
...@@ -157,9 +323,14 @@ directive_module.directive('apollonspanel', ...@@ -157,9 +323,14 @@ directive_module.directive('apollonspanel',
} else if (namespace_view_type.HISTORY == viewType) { } else if (namespace_view_type.HISTORY == viewType) {
loadCommitHistory(namespace); loadCommitHistory(namespace);
} else { } else if (namespace_view_type.INSTANCE == viewType) {
loadInstanceInfo(namespace); refreshInstancesInfo(namespace);
}
} }
function switchInstanceViewType(namespace, type) {
namespace.instanceViewType = type;
loadInstanceInfo(namespace);
} }
function loadCommitHistory(namespace) { function loadCommitHistory(namespace) {
...@@ -171,7 +342,7 @@ directive_module.directive('apollonspanel', ...@@ -171,7 +342,7 @@ directive_module.directive('apollonspanel',
var size = 10; var size = 10;
CommitService.find_commits(scope.appId, CommitService.find_commits(scope.appId,
scope.env, scope.env,
scope.cluster, namespace.baseInfo.clusterName,
namespace.baseInfo.namespaceName, namespace.baseInfo.namespaceName,
namespace.commitPage, namespace.commitPage,
size) size)
...@@ -191,12 +362,11 @@ directive_module.directive('apollonspanel', ...@@ -191,12 +362,11 @@ directive_module.directive('apollonspanel',
}); });
} }
function switchInstanceViewType(namespace, type) {
namespace.instanceViewType = type;
loadInstanceInfo(namespace);
}
function loadInstanceInfo(namespace) { function loadInstanceInfo(namespace) {
var size = 20;
if (namespace.isBranch) {
size = 2000;
}
var type = namespace.instanceViewType; var type = namespace.instanceViewType;
...@@ -204,7 +374,7 @@ directive_module.directive('apollonspanel', ...@@ -204,7 +374,7 @@ directive_module.directive('apollonspanel',
if (!namespace.latestRelease) { if (!namespace.latestRelease) {
ReleaseService.findActiveReleases(scope.appId, ReleaseService.findActiveReleases(scope.appId,
scope.env, scope.env,
scope.cluster, namespace.baseInfo.clusterName,
namespace.baseInfo.namespaceName, namespace.baseInfo.namespaceName,
0, 1).then(function (result) { 0, 1).then(function (result) {
...@@ -217,7 +387,8 @@ directive_module.directive('apollonspanel', ...@@ -217,7 +387,8 @@ directive_module.directive('apollonspanel',
namespace.latestRelease = latestRelease; namespace.latestRelease = latestRelease;
InstanceService.findInstancesByRelease(scope.env, InstanceService.findInstancesByRelease(scope.env,
latestRelease.id, latestRelease.id,
namespace.latestReleaseInstancesPage) namespace.latestReleaseInstancesPage,
size)
.then(function (result) { .then(function (result) {
namespace.latestReleaseInstances = result; namespace.latestReleaseInstances = result;
namespace.latestReleaseInstancesPage++; namespace.latestReleaseInstancesPage++;
...@@ -226,7 +397,8 @@ directive_module.directive('apollonspanel', ...@@ -226,7 +397,8 @@ directive_module.directive('apollonspanel',
} else { } else {
InstanceService.findInstancesByRelease(scope.env, InstanceService.findInstancesByRelease(scope.env,
namespace.latestRelease.id, namespace.latestRelease.id,
namespace.latestReleaseInstancesPage) namespace.latestReleaseInstancesPage,
size)
.then(function (result) { .then(function (result) {
if (result && result.content.length) { if (result && result.content.length) {
namespace.latestReleaseInstancesPage++; namespace.latestReleaseInstancesPage++;
...@@ -283,6 +455,7 @@ directive_module.directive('apollonspanel', ...@@ -283,6 +455,7 @@ directive_module.directive('apollonspanel',
scope.env, scope.env,
scope.cluster, scope.cluster,
namespace.baseInfo.namespaceName, namespace.baseInfo.namespaceName,
'',
namespace.allInstancesPage) namespace.allInstancesPage)
.then(function (result) { .then(function (result) {
if (result && result.content.length) { if (result && result.content.length) {
...@@ -304,15 +477,96 @@ directive_module.directive('apollonspanel', ...@@ -304,15 +477,96 @@ directive_module.directive('apollonspanel',
namespace.latestReleaseInstances = []; namespace.latestReleaseInstances = [];
namespace.latestRelease = undefined; namespace.latestRelease = undefined;
if (!namespace.isBranch) {
namespace.notLatestReleaseNames = []; namespace.notLatestReleaseNames = [];
namespace.notLatestReleaseInstances = {}; namespace.notLatestReleaseInstances = {};
namespace.allInstancesPage = 0; namespace.allInstancesPage = 0;
namespace.allInstances = []; namespace.allInstances = [];
}
getInstanceCountByNamespace(namespace); initNamespaceInstancesCount(namespace);
loadInstanceInfo(namespace); loadInstanceInfo(namespace);
}
function initRules(branch) {
NamespaceBranchService.findBranchGrayRules(scope.appId,
scope.env,
scope.cluster,
scope.namespace.baseInfo.namespaceName,
branch.baseInfo.clusterName)
.then(function (result) {
if (result.appId) {
branch.rules = result;
}
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载灰度规则出错");
});
}
function addRuleItem(branch) {
var newRuleItem = {
clientAppId: !branch.parentNamespace.isPublic ? branch.baseInfo.appId : '',
clientIpList: [],
draftIpList: [],
isNew: true
};
branch.editingRuleItem = newRuleItem;
EventManager.emit(EventManager.EventType.EDIT_GRAY_RELEASE_RULES, {
branch: branch
});
}
function editRuleItem(branch, ruleItem) {
ruleItem.isNew = false;
ruleItem.draftIpList = _.clone(ruleItem.clientIpList);
branch.editingRuleItem = ruleItem;
EventManager.emit(EventManager.EventType.EDIT_GRAY_RELEASE_RULES, {
branch: branch
});
}
function deleteRuleItem(branch, ruleItem) {
branch.rules.ruleItems.forEach(function (item, index) {
if (item.clientAppId == ruleItem.clientAppId) {
branch.rules.ruleItems.splice(index, 1);
toastr.success("删除成功");
}
});
useRules(branch);
}
function useRules(branch) {
NamespaceBranchService.updateBranchGrayRules(scope.appId,
scope.env,
scope.cluster,
scope.namespace.baseInfo.namespaceName,
branch.baseInfo.clusterName,
branch.rules
)
.then(function (result) {
toastr.success('灰度规则更新成功');
//show tips if branch has not release configs
if (branch.itemModifiedCnt) {
AppUtil.showModal("#updateRuleTips");
}
setTimeout(function () {
refreshInstancesInfo(branch);
}, 1500);
}, function (result) {
AppUtil.showErrorMsg(result, "灰度规则更新失败");
})
} }
function toggleTextEditStatus(namespace) { function toggleTextEditStatus(namespace) {
...@@ -344,11 +598,40 @@ directive_module.directive('apollonspanel', ...@@ -344,11 +598,40 @@ directive_module.directive('apollonspanel',
} }
function modifyByText(namespace) { function modifyByText(namespace) {
if (scope.commitChange(namespace)) { var model = {
configText: namespace.editText,
namespaceId: namespace.baseInfo.id,
format: namespace.format
};
//prevent repeat submit
if (namespace.commitChangeBtnDisabled) {
return;
}
namespace.commitChangeBtnDisabled = true;
ConfigService.modify_items(scope.appId,
scope.env,
scope.cluster,
namespace.baseInfo.namespaceName,
model).then(
function (result) {
toastr.success("更新成功, 如需生效请发布");
//refresh all namespace items
EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE,
{
namespace: namespace
});
return true;
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "更新失败");
namespace.commitChangeBtnDisabled = false;
return false;
}
);
namespace.commited = true; namespace.commited = true;
toggleTextEditStatus(namespace); toggleTextEditStatus(namespace);
} }
}
function goToParentAppConfigPage(namespace) { function goToParentAppConfigPage(namespace) {
$window.location.href = "/config.html?#/appid=" + namespace.parentAppId; $window.location.href = "/config.html?#/appid=" + namespace.parentAppId;
...@@ -418,6 +701,47 @@ directive_module.directive('apollonspanel', ...@@ -418,6 +701,47 @@ directive_module.directive('apollonspanel',
namespace.viewItems = items; namespace.viewItems = items;
} }
//normal release and gray release
function publish(namespace) {
if (!namespace.hasReleasePermission) {
AppUtil.showModal('#releaseNoPermissionDialog');
return;
} else if (namespace.lockOwner && scope.user == namespace.lockOwner) {
//can not publish if config modified by himself
AppUtil.showModal('#releaseDenyDialog');
return;
} }
if (namespace.isBranch) {
namespace.mergeAndPublish = false;
} }
EventManager.emit(EventManager.EventType.PUBLISH_NAMESPACE,
{
namespace: namespace
}); });
}
function mergeAndPublish(branch) {
var parentNamespace = branch.parentNamespace;
if (!parentNamespace.hasReleasePermission) {
AppUtil.showModal('#releaseNoPermissionDialog');
}else if (branch.lockOwner && scope.user == branch.lockOwner) {
AppUtil.showModal('#releaseDenyDialog');
} else if (parentNamespace.itemModifiedCnt > 0) {
AppUtil.showModal('#mergeAndReleaseDenyDialog');
}
else {
EventManager.emit(EventManager.EventType.MERGE_AND_PUBLISH_NAMESPACE, {branch: branch});
}
}
function rollback(namespace) {
EventManager.emit(EventManager.EventType.PRE_ROLLBACK_NAMESPACE, {namespace: namespace});
}
}
}
}
directive_module.directive('releasemodal', releaseModalDirective);
function releaseModalDirective(toastr, AppUtil, EventManager, ReleaseService, NamespaceBranchService) {
return {
restrict: 'E',
templateUrl: '../../views/component/release-modal.html',
transclude: true,
replace: true,
scope: {
appId: '=',
env: '=',
cluster: '='
},
link: function (scope) {
scope.switchReleaseChangeViewType = switchReleaseChangeViewType;
scope.release = release;
scope.releaseBtnDisabled = false;
scope.releaseChangeViewType = 'change';
scope.releaseComment = '';
EventManager.subscribe(EventManager.EventType.PUBLISH_NAMESPACE,
function (context) {
var namespace = context.namespace;
scope.toReleaseNamespace = context.namespace;
var date = new Date().Format("yyyyMMddhhmmss");
if (namespace.mergeAndPublish) {
namespace.releaseTitle = date + "-gray-release-merge-to-master";
} else if (namespace.isBranch) {
namespace.releaseTitle = date + "-gray";
} else {
namespace.releaseTitle = date + "-release";
}
AppUtil.showModal('#releaseModal');
});
function release() {
if (scope.toReleaseNamespace.mergeAndPublish) {
mergeAndPublish();
} else {
publish();
}
}
function publish() {
scope.releaseBtnDisabled = true;
ReleaseService.release(scope.appId, scope.env,
scope.toReleaseNamespace.baseInfo.clusterName,
scope.toReleaseNamespace.baseInfo.namespaceName,
scope.toReleaseNamespace.releaseTitle,
scope.releaseComment).then(
function (result) {
AppUtil.hideModal('#releaseModal');
toastr.success("发布成功");
//refresh all namespace items
scope.releaseBtnDisabled = false;
//gray release
if (scope.toReleaseNamespace.isBranch
&& !scope.toReleaseNamespace.mergeAndPublish) {
//refresh item status
scope.toReleaseNamespace.branchItems.forEach(function (item, index) {
if (item.isDeleted) {
scope.toReleaseNamespace.branchItems.splice(index, 1);
} else {
item.isModified = false;
}
});
scope.toReleaseNamespace.itemModifiedCnt = 0;
//check rules
if (!scope.toReleaseNamespace.rules
|| !scope.toReleaseNamespace.rules.ruleItems
|| !scope.toReleaseNamespace.rules.ruleItems.length) {
scope.toReleaseNamespace.viewType = 'rule';
AppUtil.showModal('#grayReleaseWithoutRulesTips');
}
} else {
EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE,
{
namespace: scope.toReleaseNamespace
})
}
}, function (result) {
scope.releaseBtnDisabled = false;
toastr.error(AppUtil.errorMsg(result), "发布失败");
}
);
}
function mergeAndPublish() {
NamespaceBranchService.mergeAndReleaseBranch(scope.appId,
scope.env,
scope.cluster,
scope.toReleaseNamespace.baseInfo.namespaceName,
scope.toReleaseNamespace.baseInfo.clusterName,
scope.toReleaseNamespace.releaseTitle,
scope.releaseComment,
scope.toReleaseNamespace.mergeAfterDeleteBranch)
.then(function (result) {
toastr.success("全量发布成功");
EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE,
{
namespace: scope.toReleaseNamespace
})
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "全量发布失败");
});
AppUtil.hideModal('#releaseModal');
}
function switchReleaseChangeViewType(type) {
scope.releaseChangeViewType = type;
}
}
}
}
directive_module.directive('rollbackmodal', rollbackModalDirective);
function rollbackModalDirective(AppUtil, EventManager, ReleaseService, toastr) {
return {
restrict: 'E',
templateUrl: '../../views/component/rollback-modal.html',
transclude: true,
replace: true,
scope: {
appId: '=',
env: '=',
cluster: '='
},
link: function (scope) {
scope.showRollbackAlertDialog = showRollbackAlertDialog;
EventManager.subscribe(EventManager.EventType.PRE_ROLLBACK_NAMESPACE,
function (context) {
preRollback(context.namespace);
});
EventManager.subscribe(EventManager.EventType.ROLLBACK_NAMESPACE,
function (context) {
rollback();
});
function preRollback(namespace) {
scope.toRollbackNamespace = namespace;
//load latest two active releases
ReleaseService.findActiveReleases(scope.appId,
scope.env,
scope.cluster,
scope.toRollbackNamespace.baseInfo.namespaceName,
0, 2)
.then(function (result) {
if (result.length <= 1) {
toastr.error("没有可以回滚的发布历史");
return;
}
scope.toRollbackNamespace.firstRelease = result[0];
scope.toRollbackNamespace.secondRelease = result[1];
ReleaseService.compare(scope.env,
scope.toRollbackNamespace.firstRelease.id,
scope.toRollbackNamespace.secondRelease.id)
.then(function (result) {
scope.toRollbackNamespace.releaseCompareResult = result.changes;
AppUtil.showModal('#rollbackModal');
})
});
}
function rollback() {
scope.toRollbackNamespace.rollbackBtnDisabled = true;
ReleaseService.rollback(scope.env,
scope.toRollbackNamespace.firstRelease.id)
.then(function (result) {
toastr.success("回滚成功");
scope.toRollbackNamespace.rollbackBtnDisabled = false;
AppUtil.hideModal('#rollbackModal');
EventManager.emit(EventManager.EventType.REFRESH_NAMESPACE,
{
namespace:scope.toRollbackNamespace
});
}, function (result) {
scope.toRollbackNamespace.rollbackBtnDisabled = false;
AppUtil.showErrorMsg(result, "回滚失败");
})
}
function showRollbackAlertDialog() {
AppUtil.hideModal("#rollbackModal");
AppUtil.showModal("#rollbackAlertDialog");
}
}
}
}
directive_module.directive('showtextmodal', showTextModalDirective);
function showTextModalDirective() {
return {
restrict: 'E',
templateUrl: '../../views/component/show-text-modal.html',
transclude: true,
replace: true,
scope: {
text: '='
},
link: function (scope) {
}
}
}
appService.service("ConfigService", ['$resource', '$q', function ($resource, $q) { appService.service("ConfigService", ['$resource', '$q', function ($resource, $q) {
var config_source = $resource("", {}, { var config_source = $resource("", {}, {
load_namespace: {
method: 'GET',
isArray: false,
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName'
},
load_all_namespaces: { load_all_namespaces: {
method: 'GET', method: 'GET',
isArray: true, isArray: true,
...@@ -39,6 +44,20 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q) ...@@ -39,6 +44,20 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
}); });
return { return {
load_namespace: function (appId, env, clusterName, namespaceName) {
var d = $q.defer();
config_source.load_namespace({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName
}, function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
},
load_all_namespaces: function (appId, env, clusterName) { load_all_namespaces: function (appId, env, clusterName) {
var d = $q.defer(); var d = $q.defer();
config_source.load_all_namespaces({ config_source.load_all_namespaces({
......
appService.service('EventManager', [function () {
/**
* subscribe EventType with any object
* @type {string}
*/
var ALL_OBJECT = '*';
var eventRegistry = {};
/**
*
* @param eventType acquired. event type
* @param context optional. event execute context
* @param objectId optional. subscribe object id and empty value means subscribe event type with all object
*/
function emit(eventType, context, objectId) {
if (!eventType) {
return;
}
if (!eventRegistry[eventType]) {
return;
}
if (!context) {
context = {};
}
if (!objectId) {
objectId = ALL_OBJECT;
}
var subscribers = eventRegistry[eventType][objectId];
emitEventToSubscribers(subscribers, context);
if (objectId == ALL_OBJECT) {
return;
}
//emit event to subscriber which subscribed all object
subscribers = eventRegistry[eventType][ALL_OBJECT];
emitEventToSubscribers(subscribers);
}
function emitEventToSubscribers(subscribers, context) {
if (subscribers) {
subscribers.forEach(function (subscriber) {
subscriber.callback(context);
})
}
}
/**
*
* @param eventType acquired. event type
* @param callback acquired. callback function when event emitted
* @param objectId optional. subscribe object id and empty value means subscribe event type with all object
*/
function subscribe(eventType, callback, objectId) {
if (!eventType || !callback) {
return;
}
if (!objectId) {
objectId = ALL_OBJECT;
}
var subscribedObjects = eventRegistry[eventType];
if (!subscribedObjects) {
subscribedObjects = {};
eventRegistry[eventType] = subscribedObjects;
}
var callbacks = subscribedObjects[objectId];
if (!callbacks) {
callbacks = [];
subscribedObjects[objectId] = callbacks;
}
var subscriber = {
id: Math.random() * Math.random(),
callback: callback
};
callbacks.push(subscriber);
return subscriber.id;
}
/**
*
* @param eventType acquired. event type
* @param subscriberId acquired. subscriber id which get from event manager when subscribe
* @param objectId optional. subscribe object id and empty value means subscribe event type with all object
*/
function unsubscribe(eventType, subscriberId, objectId) {
if (!eventType || !subscriberId) {
return;
}
if (!objectId) {
objectId = ALL_OBJECT;
}
var subscribers = eventRegistry[eventType] ?
eventRegistry[eventType][objectId] : undefined;
if (!subscribers) {
return;
}
subscribers.forEach(function (subscriber, index) {
if (subscriber.id == subscriberId) {
subscribers.splice(index, 1);
}
})
}
return {
ALL_OBJECT: ALL_OBJECT,
emit: emit,
subscribe: subscribe,
unsubscribe: unsubscribe,
EventType: {
REFRESH_NAMESPACE: 'refresh_namespace',
PUBLISH_NAMESPACE: 'pre_public_namespace',
MERGE_AND_PUBLISH_NAMESPACE: 'merge_and_publish_namespace',
PRE_ROLLBACK_NAMESPACE: 'pre_rollback_namespace',
ROLLBACK_NAMESPACE: 'rollback_namespace',
EDIT_GRAY_RELEASE_RULES: 'edit_gray_release_rules',
UPDATE_GRAY_RELEASE_RULES: 'update_gray_release_rules'
}
}
}]);
...@@ -21,13 +21,17 @@ appService.service('InstanceService', ['$resource', '$q', function ($resource, $ ...@@ -21,13 +21,17 @@ appService.service('InstanceService', ['$resource', '$q', function ($resource, $
} }
}); });
return { var instanceService = {
findInstancesByRelease: function (env, releaseId, page) { findInstancesByRelease: function (env, releaseId, page, size) {
if (!size) {
size = 20;
}
var d = $q.defer(); var d = $q.defer();
resource.find_instances_by_release({ resource.find_instances_by_release({
env: env, env: env,
releaseId: releaseId, releaseId: releaseId,
page: page page: page,
size: size
}, },
function (result) { function (result) {
d.resolve(result); d.resolve(result);
...@@ -36,18 +40,32 @@ appService.service('InstanceService', ['$resource', '$q', function ($resource, $ ...@@ -36,18 +40,32 @@ appService.service('InstanceService', ['$resource', '$q', function ($resource, $
}); });
return d.promise; return d.promise;
}, },
findInstancesByNamespace: function (appId, env, clusterName, namespaceName, page) { findInstancesByNamespace: function (appId, env, clusterName, namespaceName, instanceAppId, page, size) {
if (!size) {
size = 20;
}
var d = $q.defer(); var d = $q.defer();
var instanceAppIdRequest = instanceAppId;
instanceService.lastInstanceAppIdRequest = instanceAppIdRequest;
resource.find_instances_by_namespace({ resource.find_instances_by_namespace({
env: env, env: env,
appId: appId, appId: appId,
clusterName: clusterName, clusterName: clusterName,
namespaceName: namespaceName, namespaceName: namespaceName,
page: page instanceAppId: instanceAppId,
page: page,
size: size
}, },
function (result) { function (result) {
if (instanceAppIdRequest
!= instanceService.lastInstanceAppIdRequest) {
return;
}
d.resolve(result); d.resolve(result);
}, function (result) { }, function (result) {
if (instanceAppIdRequest != instanceService.lastInstanceAppIdRequest) {
return;
}
d.reject(result); d.reject(result);
}); });
return d.promise; return d.promise;
...@@ -84,5 +102,7 @@ appService.service('InstanceService', ['$resource', '$q', function ($resource, $ ...@@ -84,5 +102,7 @@ appService.service('InstanceService', ['$resource', '$q', function ($resource, $
return d.promise; return d.promise;
} }
} };
return instanceService;
}]); }]);
appService.service('NamespaceBranchService', ['$resource', '$q', function ($resource, $q) {
var resource = $resource('', {}, {
find_namespace_branch: {
method: 'GET',
isArray: false,
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/branches'
},
create_branch: {
method: 'POST',
isArray: false,
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/branches'
},
delete_branch: {
method: 'DELETE',
isArray: false,
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/branches/:branchName'
},
merge_and_release_branch: {
method: 'POST',
isArray: false,
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/branches/:branchName/merge'
},
find_branch_gray_rules: {
method: 'GET',
isArray: false,
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/branches/:branchName/rules'
},
update_branch_gray_rules: {
method: 'PUT',
isArray: false,
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/branches/:branchName/rules'
}
});
function find_namespace_branch(appId, env, clusterName, namespaceName) {
var d = $q.defer();
resource.find_namespace_branch({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
}
function create_branch(appId, env, clusterName, namespaceName) {
var d = $q.defer();
resource.create_branch({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName
}, {},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
}
function delete_branch(appId, env, clusterName, namespaceName, branchName) {
var d = $q.defer();
resource.delete_branch({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName,
branchName: branchName
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
}
function merge_and_release_branch(appId, env, clusterName, namespaceName,
branchName, title, comment, deleteBranch) {
var d = $q.defer();
resource.merge_and_release_branch({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName,
branchName: branchName,
deleteBranch:deleteBranch
}, {
releaseTitle: title,
releaseComment: comment
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
}
function find_branch_gray_rules(appId, env, clusterName, namespaceName, branchName) {
var d = $q.defer();
resource.find_branch_gray_rules({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName,
branchName: branchName
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
}
function update_branch_gray_rules(appId, env, clusterName,
namespaceName, branchName, newRules) {
var d = $q.defer();
resource.update_branch_gray_rules({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName,
branchName: branchName
}, newRules,
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
}
return {
findNamespaceBranch: find_namespace_branch,
createBranch: create_branch,
deleteBranch: delete_branch,
mergeAndReleaseBranch: merge_and_release_branch,
findBranchGrayRules: find_branch_gray_rules,
updateBranchGrayRules: update_branch_gray_rules
}
}]);
appService.service('ReleaseHistoryService', ['$resource', '$q', function ($resource, $q) {
var resource = $resource('', {}, {
find_release_history_by_namespace: {
method: 'GET',
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/releases/histories',
isArray: true
}
});
function findReleaseHistoryByNamespace(appId, env, clusterName, namespaceName, page, size) {
var d = $q.defer();
resource.find_release_history_by_namespace({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName,
page: page,
size: size
}, function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
}
return {
findReleaseHistoryByNamespace: findReleaseHistoryByNamespace
}
}]);
...@@ -76,12 +76,12 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q ...@@ -76,12 +76,12 @@ appService.service('ReleaseService', ['$resource', '$q', function ($resource, $q
return d.promise; return d.promise;
} }
function compare(env, firstReleaseId, secondReleaseId) { function compare(env, baseReleaseId, toCompareReleaseId) {
var d = $q.defer(); var d = $q.defer();
resource.compare({ resource.compare({
env: env, env: env,
firstReleaseId: firstReleaseId, baseReleaseId: baseReleaseId,
secondReleaseId: secondReleaseId toCompareReleaseId: toCompareReleaseId
}, function (result) { }, function (result) {
d.resolve(result); d.resolve(result);
}, function (result) { }, function (result) {
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
<form class="form-horizontal" ng-controller="ServerConfigController" ng-submit="create()"> <form class="form-horizontal" ng-controller="ServerConfigController" ng-submit="create()">
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label"> <label class="col-sm-2 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
key</label> key</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input type="text" class="form-control" name="key" ng-model="serverConfig.key" <input type="text" class="form-control" name="key" ng-model="serverConfig.key"
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-2 control-label"> <label class="col-sm-2 control-label">
<apollorequiredfiled></apollorequiredfiled> <apollorequiredfield></apollorequiredfield>
value</label> value</label>
<div class="col-sm-9"> <div class="col-sm-9">
<textarea class="form-control" rows="4" name="comment" ng-model="serverConfig.value"></textarea> <textarea class="form-control" rows="4" name="comment" ng-model="serverConfig.value"></textarea>
......
...@@ -29,6 +29,10 @@ p, td, span { ...@@ -29,6 +29,10 @@ p, td, span {
border-radius: 0; border-radius: 0;
} }
.no-border {
border: 0;
}
.no-margin { .no-margin {
margin: 0; margin: 0;
} }
...@@ -37,6 +41,27 @@ p, td, span { ...@@ -37,6 +41,27 @@ p, td, span {
cursor: pointer; cursor: pointer;
} }
.word-break {
word-wrap: break-word;
word-break: break-all;
}
.border {
border: solid 1px #c3c3c3;
}
.active {
background: #f5f5f5;
}
.label-default-light {
background: #A4A4A4
}
.panel-default .panel-heading {
color: #797979;
}
pre { pre {
white-space: pre-wrap; /* Since CSS 2.1 */ white-space: pre-wrap; /* Since CSS 2.1 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
...@@ -88,11 +113,11 @@ pre { ...@@ -88,11 +113,11 @@ pre {
.badge-white { .badge-white {
background: #ffffff; background: #ffffff;
color: #0f0f0f; color: #6c6c6c;
} }
.panel-default > .panel-heading .badge { .modal-dialog {
width: 960px;
} }
.apollo-container { .apollo-container {
...@@ -208,18 +233,20 @@ table th { ...@@ -208,18 +233,20 @@ table th {
} }
.panel-heading .header-buttons { .panel-heading .header-buttons {
float: right; /*float: right;*/
min-width: 405px; min-width: 405px;
} }
#treeview .list-group{ #treeview .list-group {
margin: 0; margin: 0;
} }
#treeview .list-group .list-group-item{
#treeview .list-group .list-group-item {
border: 0; border: 0;
border-top: 1px solid #eff2f7; border-top: 1px solid #eff2f7;
} }
.project-info th { .project-info th {
text-align: right; text-align: right;
padding: 4px 6px; padding: 4px 6px;
...@@ -235,14 +262,6 @@ table th { ...@@ -235,14 +262,6 @@ table th {
min-height: 500px; min-height: 500px;
} }
#itemModal .modal-dialog {
width: 860px;
}
#itemModal .modal-body {
padding-bottom: 0;
}
#config-edit { #config-edit {
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 3px; border-radius: 3px;
...@@ -315,57 +334,79 @@ table th { ...@@ -315,57 +334,79 @@ table th {
margin-bottom: 2px; margin-bottom: 2px;
} }
.namespace-attribute-panel { .namespace-panel {
border-top: 0;
}
.namespace-panel .namespace-name {
font-size: 20px;
}
.namespace-panel .namespace-label {
margin-left: 5px;
}
.namespace-panel .namespace-attribute-panel {
margin-left: 0; margin-left: 0;
color: #fff; color: #fff;
border-top: 0; border-top: 0;
background: #f1f2f7; background: #f1f2f7;
} }
.namespace-attribute-public { .namespace-panel .namespace-attribute-public {
background: #31708f; background: #31708f;
width: 40px; width: 40px;
cursor: pointer; cursor: pointer;
} }
.second-panel-heading .nav-tabs { .namespace-panel .second-panel-heading .nav-tabs {
border-bottom: 0; border-bottom: 0;
} }
.namespace-view-table td img { .namespace-panel .namespace-view-table td img {
cursor: pointer; cursor: pointer;
width: 23px; width: 23px;
height: 23px; height: 23px;
} }
.namespace-view-table table { .namespace-panel .namespace-view-table table {
table-layout: inherit; table-layout: inherit;
} }
.namespace-view-table td { .namespace-panel .namespace-view-table td {
word-wrap: break-word; word-wrap: break-word;
} }
.namespace-view-table .glyphicon { .namespace-panel .namespace-view-table .glyphicon {
cursor: pointer; cursor: pointer;
} }
.history-view { .namespace-panel .namespace-view-table .search-input {
padding-top: 15px;
padding-bottom: 10px;
}
.namespace-panel .history-view {
padding: 10px 20px; padding: 10px 20px;
} }
.instance-view .btn-primary .badge { .namespace-panel .instance-view .btn-primary .badge {
color: #337ab7; color: #337ab7;
background-color: #fff; background-color: #fff;
} }
.instance-view .btn-default .badge { .namespace-panel .instance-view .btn-default .badge {
background: #777; background: #777;
color: #fff; color: #fff;
} }
.namespace-panel .rules-manage-view {
padding: 45px 20px;
}
.line { .line {
width: 20px; width: 20px;
border: 1px solid #ddd; border: 1px solid #ddd;
...@@ -399,8 +440,7 @@ table th { ...@@ -399,8 +440,7 @@ table th {
.list-group-item .btn-title { .list-group-item .btn-title {
color: gray; color: gray;
font-family: "Apple Color Emoji"; font-size: 14px;
font-size: 16px;
margin: 0; margin: 0;
} }
...@@ -462,12 +502,13 @@ table th { ...@@ -462,12 +502,13 @@ table th {
background: #c3c3c3; background: #c3c3c3;
} }
.user-container { .item-container {
border: solid 1px #f1f2f7;
margin-top: 15px; margin-top: 15px;
padding: 20px 15px padding: 20px 15px
} }
.user-container .user-info { .item-container .item-info {
margin-left: 5px; margin-left: 5px;
} }
...@@ -488,50 +529,97 @@ table th { ...@@ -488,50 +529,97 @@ table th {
margin-top: 10px; margin-top: 10px;
} }
.release-history .icon { .release-history .release-history-container {
font-size: 13px; padding: 0;
} }
.release-history .info { .release-history .release-history-list {
font-size: 13px; max-height: 750px;
margin-left: 5px; padding: 0;
margin-top: 2px; border-right: solid 1px #eff2f7;
overflow: scroll;
} }
.release-history .user .info { .release-history .release-history-list .media {
font-size: 22px; position: relative;
margin: 0;
padding: 10px;
border-bottom: solid 1px #eff2f7;
} }
.release-history .time { .release-history .release-history-list .release-operation {
padding-top: 8px; position: absolute;
right: 0;
top: 0;
width: 5px;
height: 100%;
} }
.release-history .comment { .release-history .release-history-list .media .media-left {
padding-top: 10px; padding-top: 10px;
} }
.release-history .table { .release-history .release-history-list .media .media-body .release-title {
margin-top: 15px; padding: 0;
} }
.release-history .badge-0 { .release-history .release-history-list .load-more {
background: #f0ad4e; height: 45px;
background: #f5f5f5;
} }
.release-history .badge-1 { .release-history .release-operation-normal {
background: #c3c3c3; background: #316510;
}
.release-history .release-operation-rollback {
background: #997f1c;
}
.release-history .release-operation-gray {
background: #999999;
}
.release-history .operation-caption-container {
position: relative;
}
.release-history .section-title {
padding: 15px 10px 0 10px;
}
.release-history .operation-caption {
position: absolute;
top: 45px;
width: 75px;
height: 18px;
color: #fff;
font-size: 12px;
}
.release-history .panel-heading .back-btn {
position: absolute;
top: 45px;
right: 10px;
} }
.release-history .badge-2 { .release-history .release-info {
background: #27AE60; padding: 0;
border: 0;
} }
.release-history .badge-3 { .release-history .panel-heading {
background: #3478a8; padding: 15px;
} }
.release-history .label { .release-history .panel-body {
margin-right: 15px; max-height: 750px;
padding: 0;
overflow: scroll;
}
.release-history .empty-container {
padding: 15px;
} }
.valdr-message { .valdr-message {
...@@ -553,10 +641,11 @@ table th { ...@@ -553,10 +641,11 @@ table th {
} }
/*index page*/ /*index page*/
#app-list h5{ #app-list h5 {
word-wrap: break-word; word-wrap: break-word;
word-break: break-all; word-break: break-all;
} }
#app-list .media-body { #app-list .media-body {
padding-top: 15px; padding-top: 15px;
} }
...@@ -611,10 +700,34 @@ table th { ...@@ -611,10 +700,34 @@ table th {
background: #57c8f2; background: #57c8f2;
} }
.favorites-app-list .no-favorites{ .favorites-app-list .no-favorites {
padding-bottom: 15px; padding-bottom: 15px;
} }
.visit-app-list .media-left { .visit-app-list .media-left {
background: #41cac0; background: #41cac0;
} }
#rulesModal .rules-ip-selector {
width: 500px;
height: 50px;
}
#rulesModal textarea {
width: 500px;
margin-bottom: 5px;
}
#rulesModal .rule-edit-panel {
padding: 15px 0;
}
#rulesModal .add-rule {
margin-left: 15px;
}
#rulesModal .select2-container .select2-search__field:not([placeholder='']) {
width: auto !important;
}
...@@ -10,7 +10,8 @@ ...@@ -10,7 +10,8 @@
{{detail}} {{detail}}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" ng-show="showCancelBtn">取消</button> <button type="button" class="btn btn-default" data-dismiss="modal"
ng-show="showCancelBtn" ng-click="cancel()">取消</button>
<button type="button" class="btn btn-danger" data-dismiss="modal" <button type="button" class="btn btn-danger" data-dismiss="modal"
ng-click="confirm()"> ng-click="confirm()">
确定 确定
......
<pre id="{{apolloId}}"> <pre class="no-radius" id="{{apolloId}}">
</pre> </pre>
<form id="rulesModal" class="modal fade" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">
编辑灰度规则
</h4>
</div>
<div class="modal-body form-horizontal">
<div class="form-group"
ng-show="branch.parentNamespace.isPublic && !branch.parentNamespace.isLinkedNamespace">
<label class="control-label col-md-3 text-right">
<apollorequiredfield></apollorequiredfield>
灰度的AppId</label>
<div class="col-md-4">
<input type="text" class="form-control"
ng-model="branch.editingRuleItem.clientAppId"
ng-model-options='{ debounce: 300 }'
ng-change='initSelectIps()'
>
</div>
</div>
<div class="form-group"
ng-show="branch.parentNamespace.isPublic && !branch.parentNamespace.isLinkedNamespace">
<label class="control-label col-md-3 text-right">灰度应用规则</label>
<div class="col-md-9">
<label class="form-control-static radio-inline">
<input type="radio" name="ApplyToAllInstances" value="false"
ng-checked="!branch.editingRuleItem.ApplyToAllInstances"
ng-click="branch.editingRuleItem.ApplyToAllInstances = false">
应用到部分实例
</label>
<label class="form-control-static radio-inline">
<input type="radio" name="ApplyToAllInstances" value="true"
ng-checked="branch.editingRuleItem.ApplyToAllInstances"
ng-click="branch.editingRuleItem.ApplyToAllInstances = true">
应用到所有的实例
</label>
</div>
</div>
<div class="form-group" ng-show="!branch.editingRuleItem.ApplyToAllInstances">
<label class="control-label col-md-3 text-right">
<apollorequiredfield></apollorequiredfield>
灰度的IP</label>
<div class="col-md-9">
<div class="form-inline">
<div class="form-group">
<select class="rules-ip-selector" multiple="multiple">
<option ng-repeat="instance in selectIps"
ng-bind="instance.ip">
</option>
</select>
<div ng-show="branch.parentNamespace.isPublic && !branch.parentNamespace.isLinkedNamespace">
<small>(实例列表会根据输入的AppId自动过滤)</small>
</div>
<div style="margin-top: 5px">
<small>没找到你想要的IP?可以<a ng-click="manual =! manual">手动输入IP</a></small>
</div>
</div>
</div>
<div class="form-inline" ng-show="manual">
<div class="form-group">
<textarea class="form-control" ng-model="toAddIPs" rows="3"
placeholder="输入IP列表,英文逗号隔开,输入完后点击添加按钮"></textarea>
</div>
<button class="btn-default btn add-rule"
ng-click="batchAddIPs(branch, toAddIPs)">
添加
</button>
</div>
</div>
</div>
<div class="form-group" ng-show="!branch.editingRuleItem.ApplyToAllInstances">
<div class="col-md-offset-1 col-md-10 item-container">
<section class="btn-group item-info"
ng-repeat="ip in branch.editingRuleItem.draftIpList">
<button type="button" class="btn btn-default" ng-bind="ip"></button>
<button type="button" class="btn btn-default dropdown-toggle"
data-toggle="dropdown"
ng-click="removeRule(branch.editingRuleItem, ip)">
<span class="glyphicon glyphicon-remove"></span>
</button>
</section>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" ng-click="cancelEditItem(branch)">取消</button>
<button class="btn btn-primary" ng-disabled="completeEditBtnDisable"
ng-click="completeEditItem(branch)">完成
</button>
</div>
</div>
</div>
</form>
<form id="itemModal" class="modal fade" valdr-type="Item" name="itemForm"
ng-submit="doItem()">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">
<span ng-show="item.tableViewOperType == 'create' && !toOperationNamespace.isBranch"> 添加配置项</span>
<span ng-show="item.tableViewOperType == 'create' && toOperationNamespace.isBranch"> 添加灰度配置项</span>
<span ng-show="item.tableViewOperType == 'update'"> 修改配置项</span>
</h4>
</div>
<div class="modal-body form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label">
<apollorequiredfield
ng-show="item.tableViewOperType == 'create'"></apollorequiredfield>
Key
</label>
<div class="col-sm-10" valdr-form-group>
<input type="text" name="key" class="form-control" ng-model="item.key" tabindex="1"
ng-required="true" ng-disabled="item.tableViewOperType != 'create'"/>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">
<apollorequiredfield></apollorequiredfield>
Value
</label>
<div class="col-sm-10" valdr-form-group>
<textarea name="value" class="form-control" rows="6" tabindex="2"
ng-required="true"
ng-model="item.value">
</textarea>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Comment</label>
<div class="col-sm-10" valdr-form-group>
<textarea class="form-control" name="comment" ng-model="item.comment" tabindex="3"
rows="2">
</textarea>
</div>
</div>
<div class="form-group"
ng-show="item.tableViewOperType == 'create' && !toOperationNamespace.isBranch">
<label class="col-sm-2 control-label">
<apollorequiredfield></apollorequiredfield>
选择集群</label>
<div class="col-sm-10">
<apolloclusterselector apollo-app-id="appId"
apollo-default-all-checked="false"
apollo-default-checked-env="env"
apollo-default-checked-cluster="cluster"
apollo-select="collectSelectedClusters">
</apolloclusterselector>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
取消
</button>
<button type="submit" class="btn btn-primary"
ng-disabled="itemForm.$invalid || (item.addItemBtnDisabled && item.tableViewOperType == 'create')">
提交
</button>
</div>
</div>
</div>
</form>
<div class="modal fade" id="mergeAndPublishModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">全量发布</h4>
</div>
<div class="modal-body">
全量发布将会把灰度版本的配置合并到主分支,并发布。
<br>
<h5>全量发布后,您希望</h5>
<div class="radio">
<label ng-click="toReleaseNamespace.mergeAfterDeleteBranch = 'true'">
<input type="radio" name="deleteBranch"
ng-checked="!toReleaseNamespace.mergeAfterDeleteBranch ||
toReleaseNamespace.mergeAfterDeleteBranch == 'true'">
删除灰度版本
</label>
</div>
<div class="radio">
<label ng-click="toReleaseNamespace.mergeAfterDeleteBranch = 'false'">
<input type="radio" name="deleteBranch"
ng-checked="toReleaseNamespace.mergeAfterDeleteBranch == 'false'">
保留灰度版本
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" data-dismiss="modal"
ng-click="showReleaseModal()">
确定
</button>
</div>
</div>
</div>
</div>
<div class="panel" style="border-top: 0px;"> <section class="panel namespace-panel">
<div class="row namespace-attribute-panel" ng-if="namespace.isPublic">
<!--public or link label-->
<header class="row namespace-attribute-panel">
<div class="text-center namespace-attribute-public"> <div class="text-center namespace-attribute-public">
<span data-tooltip="tooltip" data-placement="bottom" title="公共的Namespace" <span data-tooltip="tooltip" data-placement="bottom"
ng-show="namespace.parentAppId == namespace.baseInfo.appId">公共</span> title="私有namespace({{namespace.baseInfo.namespaceName}})的配置只能被AppId为{{appId}}的客户端读取到"
<span data-tooltip="tooltip" data-placement="bottom" title="点击跳转到公共的Namespace" ng-show="!namespace.isPublic">私有</span>
ng-show="namespace.parentAppId != namespace.baseInfo.appId"
<span data-tooltip="tooltip" data-placement="top"
title="namespace({{namespace.baseInfo.namespaceName}})的配置能被任何客户端读取到"
ng-show="namespace.isPublic && namespace.parentAppId == namespace.baseInfo.appId">公共</span>
<span data-tooltip="tooltip" data-placement="top"
title="namespace({{namespace.baseInfo.namespaceName}})的配置将会覆盖公共namespace的配置, 且合并之后的配置只能被AppId为{{appId}}的客户端读取到"
ng-show="namespace.isPublic && namespace.isLinkedNamespace"
ng-click="goToParentAppConfigPage(namespace)">关联</span> ng-click="goToParentAppConfigPage(namespace)">关联</span>
</div> </div>
</header>
<!--branch nav-->
<header class="panel-heading second-panel-heading" ng-show="namespace.hasBranch">
<div class="row">
<div class="col-md-8 pull-left">
<ul class="nav nav-tabs">
<li role="presentation">
<a ng-class="{'node_active': namespace.currentOperateBranch == 'master'}"
ng-click="switchBranch('master')">
<img src="img/branch.png">
主版本
</a>
</li>
<li role="presentation">
<a ng-class="{'node_active': namespace.currentOperateBranch != 'master'}"
ng-click="switchBranch(namespace.branchName)">
<img src="img/branch.png">
灰度版本
</a>
</li>
</ul>
</div>
</div> </div>
</header>
<!--master panel body-->
<section class="master-panel-body"
ng-if="namespace.hasBranch && namespace.currentOperateBranch == 'master' || !namespace.hasBranch">
<!--main header-->
<header class="panel-heading"> <header class="panel-heading">
<div class="row"> <div class="row">
<div class="col-md-6 header-namespace"> <div class="col-md-6 header-namespace">
<b ng-bind="namespace.viewName" style="font-size: 20px;" data-tooltip="tooltip" data-placement="bottom" <b class="namespace-name" ng-bind="namespace.viewName"
title="{{namespace.comment}}"></b> data-tooltip="tooltip" data-placement="bottom" title="{{namespace.comment}}"></b>
<span class="label label-info no-radius" ng-bind="namespace.format"></span> <span class="label label-info no-radius namespace-label" ng-bind="namespace.format"></span>
<span class="label label-primary no-radius" ng-show="namespace.itemModifiedCnt > 0">有修改 <span class="label label-warning no-radius namespace-label" ng-show="namespace.itemModifiedCnt > 0">有修改
<span class="badge label badge-white" ng-bind="namespace.itemModifiedCnt"></span></span> <span class="badge label badge-white namespace-label" ng-bind="namespace.itemModifiedCnt"></span>
<span class="label label-warning no-radius" </span>
<span class="label label-primary no-radius namespace-label"
ng-show="namespace.lockOwner">当前修改者:{{namespace.lockOwner}}</span> ng-show="namespace.lockOwner">当前修改者:{{namespace.lockOwner}}</span>
</div> </div>
<div class="col-md-6 text-right header-buttons"> <div class="col-md-6 text-right header-buttons">
<button type="button" class="btn btn-success btn-sm J_tableview_btn" <button type="button" class="btn btn-success btn-sm"
data-tooltip="tooltip" data-placement="bottom" title="发布配置" data-tooltip="tooltip" data-placement="bottom" title="发布配置"
ng-show="namespace.hasReleasePermission || namespace.hasModifyPermission" ng-show="(namespace.hasReleasePermission || namespace.hasModifyPermission)"
ng-disabled="namespace.isTextEditing" ng-disabled="namespace.isTextEditing"
ng-click="preReleaseNs(namespace)"> ng-click="publish(namespace)">
<img src="img/release.png"> <img src="img/release.png">
发布 发布
</button> </button>
<button type="button" class="btn btn-default btn-sm J_tableview_btn" <button type="button" class="btn btn-default btn-sm"
data-tooltip="tooltip" data-placement="bottom" title="回滚已发布配置" data-tooltip="tooltip" data-placement="bottom" title="回滚已发布配置"
ng-show="namespace.hasReleasePermission" ng-show="namespace.hasReleasePermission"
ng-click="preRollback(namespace)"> ng-click="rollback(namespace)">
<img src="img/rollback.png"> <img src="img/rollback.png">
回滚 回滚
</button> </button>
<a type="button" class="btn btn-default btn-sm J_tableview_btn" <a type="button" class="btn btn-default btn-sm"
data-tooltip="tooltip" data-placement="bottom" title="查看发布历史" data-tooltip="tooltip" data-placement="bottom" title="查看发布历史"
href="/config/history.html?#/appid={{appId}}&env={{env}}&clusterName={{cluster}}&namespaceName={{namespace.baseInfo.namespaceName}}"> href="/config/history.html?#/appid={{appId}}&env={{env}}&clusterName={{cluster}}&namespaceName={{namespace.baseInfo.namespaceName}}">
<img src="img/release-history.png"> <img src="img/release-history.png">
发布历史 发布历史
</a> </a>
<a type="button" class="btn btn-default btn-sm J_tableview_btn" <a type="button" class="btn btn-default btn-sm"
data-tooltip="tooltip" data-placement="bottom" title="配置修改、发布权限" data-tooltip="tooltip" data-placement="bottom" title="配置修改、发布权限"
href="/namespace/role.html?#/appid={{appId}}&namespaceName={{namespace.baseInfo.namespaceName}}" href="/namespace/role.html?#/appid={{appId}}&namespaceName={{namespace.baseInfo.namespaceName}}"
ng-show="hasAssignUserPermission"> ng-show="hasAssignUserPermission">
<img src="img/assign.png"> <img src="img/assign.png">
授权 授权
</a> </a>
<a type="button" class="btn btn-default btn-sm J_tableview_btn"
data-tooltip="tooltip" data-placement="bottom" title="同步各环境间配置" <a type="button" class="btn btn-default btn-sm"
ng-click="goToSyncPage(namespace)" data-tooltip="tooltip" data-placement="bottom" title="创建测试版本"
ng-show="namespace.hasModifyPermission && namespace.isPropertiesFormat"> ng-show="!namespace.hasBranch && namespace.isPropertiesFormat && namespace.hasModifyPermission"
<img src="img/sync.png"> ng-click="preCreateBranch(namespace)">
同步配置 <img src="img/test.png">
创建灰度
</a> </a>
<a type="button" class="btn btn-default btn-sm J_tableview_btn" <a type="button" class="btn btn-default btn-sm J_tableview_btn"
data-tooltip="tooltip" data-placement="bottom" title="您没有任何配置权限,请申请" data-tooltip="tooltip" data-placement="bottom" title="您没有任何配置权限,请申请"
...@@ -67,9 +107,11 @@ ...@@ -67,9 +107,11 @@
</div> </div>
</header> </header>
<!--second header-->
<header class="panel-heading second-panel-heading"> <header class="panel-heading second-panel-heading">
<div class="row"> <div class="row">
<div class="col-md-8 pull-left"> <div class="col-md-8 pull-left">
<!--master branch nav tabs-->
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li role="presentation" ng-click="switchView(namespace, 'table')" <li role="presentation" ng-click="switchView(namespace, 'table')"
ng-show="namespace.isPropertiesFormat"> ng-show="namespace.isPropertiesFormat">
...@@ -78,7 +120,8 @@ ...@@ -78,7 +120,8 @@
表格 表格
</a> </a>
</li> </li>
<li role="presentation" ng-click="switchView(namespace, 'text')"> <li role="presentation"
ng-click="switchView(namespace, 'text')">
<a ng-class="{node_active:namespace.viewType == 'text'}"> <a ng-class="{node_active:namespace.viewType == 'text'}">
<img src="img/text.png"> <img src="img/text.png">
文本 文本
...@@ -126,48 +169,47 @@ ...@@ -126,48 +169,47 @@
<img src="img/submit.png" class="ns_btn"> <img src="img/submit.png" class="ns_btn">
</a> </a>
<button type="button" class="btn btn-info btn-sm" <button type="button" class="btn btn-default btn-sm"
ng-show="namespace.viewType == 'table'" data-tooltip="tooltip" data-placement="bottom" title="按Key过滤配置"
ng-show="namespace.viewType == 'table' && namespace.currentOperateBranch == 'master'"
ng-click="toggleItemSearchInput(namespace)"> ng-click="toggleItemSearchInput(namespace)">
<span class="glyphicon glyphicon-filter"></span> <span class="glyphicon glyphicon-filter"></span>
过滤配置 过滤配置
</button> </button>
<button type="button" class="btn btn-default btn-sm J_tableview_btn"
data-tooltip="tooltip" data-placement="bottom" title="同步各环境间配置"
ng-click="goToSyncPage(namespace)"
ng-show="namespace.viewType == 'table' && namespace.currentOperateBranch == 'master'
&& namespace.hasModifyPermission && namespace.isPropertiesFormat">
<img src="img/sync.png">
同步配置
</button>
<button type="button" class="btn btn-primary btn-sm" <button type="button" class="btn btn-primary btn-sm"
ng-show="namespace.viewType == 'table' && namespace.hasModifyPermission" ng-show="namespace.viewType == 'table' && namespace.currentOperateBranch == 'master'
&& namespace.hasModifyPermission"
ng-click="createItem(namespace)"> ng-click="createItem(namespace)">
<img src="img/plus.png"> <img src="img/plus.png">
新增配置 新增配置
</button> </button>
</div> </div>
</div> </div>
</header> </header>
<!--text view-->
<!--只读模式下的文本内容,不替换换行符-->
<textarea class="form-control no-radius" rows="{{namespace.itemCnt}}"
ng-show="namespace.viewType == 'text' && !namespace.isTextEditing"
ng-disabled="!namespace.isTextEditing"
ng-bind="namespace.text">
</textarea>
<!--编辑状态下的文本内容,会过滤掉换行符-->
<textarea class="form-control" rows="{{namespace.itemCnt}}" style="border-radius: 0px"
ng-show="namespace.viewType == 'text' && namespace.isTextEditing"
ng-disabled="!namespace.isTextEditing" ng-model="namespace.editText">
</textarea>
<section>
<!--table view--> <!--table view-->
<div class="namespace-view-table" ng-show="namespace.viewType == 'table'"> <div class="namespace-view-table" ng-show="namespace.viewType == 'table'">
<div class="col-md-8 col-lg-offset-2" style="padding-top: 15px; padding-bottom: 10px" <div class="col-md-8 col-lg-offset-2 search-input" ng-show="namespace.showSearchInput">
ng-show="namespace.showSearchInput"> <input type="text" class="form-control" placeholder="输入key过滤"
<input type="text" class="form-control" placeholder="输入key过滤" ng-model="namespace.searchKey" ng-model="namespace.searchKey"
ng-change="searchItems(namespace)"> ng-change="searchItems(namespace)">
</div> </div>
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>发布状态</th>
<th class="hover" title="排序" <th class="hover" title="排序"
ng-click="col='item.key';desc=!desc;"> ng-click="col='item.key';desc=!desc;">
Key&nbsp; Key&nbsp;
...@@ -191,20 +233,38 @@ ...@@ -191,20 +233,38 @@
最后修改时间 最后修改时间
<span class="glyphicon glyphicon-sort"></span> <span class="glyphicon glyphicon-sort"></span>
</th> </th>
<th> <th>
操作 操作
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="config in namespace.viewItems |orderBy:col:desc"
<tr ng-repeat="config in namespace.viewItems |orderBy:col:desc" ng-class="{warning:config.isModified}" ng-if="config.item.key">
ng-if="config.item.key && !config.isDeleted"> <td width="8%" class="text-center">
<td width="20%"> <span class="label label-warning no-radius cursor-pointer" ng-if="config.isModified"
data-tooltip="tooltip" data-placement="bottom" title="点击查看已发布的值"
ng-click="showText(config.oldValue?config.oldValue:'新增的配置,无发布的值')">未发布</span>
<span class="label label-default-light no-radius"
data-tooltip="tooltip" data-placement="bottom" title="已生效的配置"
ng-if="!config.isModified">已发布</span>
</td>
<td width="15%" class="cursor-pointer" title="点击查看" ng-click="showText(config.item.key)">
<span ng-bind="config.item.key | limitTo: 250"></span> <span ng-bind="config.item.key | limitTo: 250"></span>
<span ng-bind="config.item.key.length > 250 ? '...' :''"></span> <span ng-bind="config.item.key.length > 250 ? '...' :''"></span>
<span class="label label-default cursor-pointer" ng-if="config.hasBranchValue"
data-tooltip="tooltip" data-placement="bottom" title="该配置有灰度配置,点击查看灰度的值"
ng-click="namespace.currentOperateBranch=namespace.branchName;namespace.branch.viewType='table'"></span>
<span class="label label-success" ng-if="config.isModified && !config.oldValue"
data-tooltip="tooltip" data-placement="bottom" title="新增的配置"></span>
<span class="label label-info"
ng-if="config.isModified && config.oldValue && !config.isDeleted"
data-tooltip="tooltip" data-placement="bottom" title="修改的配置"></span>
<span class="label label-danger" ng-if="config.isDeleted"
data-tooltip="tooltip" data-placement="bottom" title="删除的配置"></span>
</td> </td>
<td width="30%" class="cursor-pointer" title="点击查看" ng-click="showText(config.item.value)"> <td width="33%" class="cursor-pointer" title="点击查看" ng-click="showText(config.item.value)">
<span ng-bind="config.item.value | limitTo: 250"></span> <span ng-bind="config.item.value | limitTo: 250"></span>
<span ng-bind="config.item.value.length > 250 ? '...': ''"></span> <span ng-bind="config.item.value.length > 250 ? '...': ''"></span>
</td> </td>
...@@ -214,11 +274,11 @@ ...@@ -214,11 +274,11 @@
</td> </td>
<td width="10%" ng-bind="config.item.dataChangeLastModifiedBy"> <td width="10%" ng-bind="config.item.dataChangeLastModifiedBy">
</td> </td>
<td width="15%" <td width="11%"
ng-bind="config.item.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'"> ng-bind="config.item.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'">
</td> </td>
<td width="10%" class="text-center"> <td width="8%" class="text-center" ng-if="!config.isDeleted">
<img src="img/edit.png" data-tooltip="tooltip" data-placement="bottom" title="修改" <img src="img/edit.png" data-tooltip="tooltip" data-placement="bottom" title="修改"
ng-click="editItem(namespace, config.item)" ng-click="editItem(namespace, config.item)"
ng-show="namespace.hasModifyPermission"> ng-show="namespace.hasModifyPermission">
...@@ -227,18 +287,32 @@ ...@@ -227,18 +287,32 @@
ng-click="preDeleteItem(namespace, config.item.id)" ng-click="preDeleteItem(namespace, config.item.id)"
ng-show="namespace.hasModifyPermission"> ng-show="namespace.hasModifyPermission">
</td> </td>
<td width="6%" class="text-center" ng-if="config.isDeleted">
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<!--text view-->
<!--只读模式下的文本内容,不替换换行符-->
<textarea class="form-control no-radius" rows="{{namespace.itemCnt}}"
ng-show="namespace.viewType == 'text' && !namespace.isTextEditing"
ng-disabled="!namespace.isTextEditing"
ng-bind="namespace.text">
</textarea>
<!--编辑状态下的文本内容,会过滤掉换行符-->
<textarea class="form-control" rows="{{namespace.itemCnt}}" style="border-radius: 0px"
ng-show="namespace.viewType == 'text' && namespace.isTextEditing"
ng-disabled="!namespace.isTextEditing" ng-model="namespace.editText">
</textarea>
<!--history view--> <!--history view-->
<div class="J_historyview history-view" ng-show="namespace.viewType == 'history'"> <div class="J_historyview history-view" ng-show="namespace.viewType == 'history'">
<div class="media" ng-repeat="commits in namespace.commits"> <div class="media" ng-repeat="commits in namespace.commits">
<div class="media-body"> <div class="media-body">
<div class="row"> <div class="row">
<div class="col-md-6"><h3 class="media-heading" ng-bind="commits.dataChangeCreatedBy"></h3></div> <div class="col-md-6"><h3 class="media-heading" ng-bind="commits.dataChangeCreatedBy"></h3>
</div>
<div class="col-md-6 text-right"><h5 class="media-heading" <div class="col-md-6 text-right"><h5 class="media-heading"
ng-bind="commits.dataChangeCreatedTime | date: 'yyyy-MM-dd HH:mm:ss'"></h5> ng-bind="commits.dataChangeCreatedTime | date: 'yyyy-MM-dd HH:mm:ss'"></h5>
</div> </div>
...@@ -246,7 +320,8 @@ ...@@ -246,7 +320,8 @@
<!--properties format--> <!--properties format-->
<table class="table table-bordered table-striped text-center table-hover" style="margin-top: 5px;" <table class="table table-bordered table-striped text-center table-hover"
style="margin-top: 5px;"
ng-if="namespace.isPropertiesFormat"> ng-if="namespace.isPropertiesFormat">
<thead> <thead>
<tr> <tr>
...@@ -281,7 +356,8 @@ ...@@ -281,7 +356,8 @@
</td> </td>
<td width="30%"> <td width="30%">
</td> </td>
<td width="30%" class="cursor-pointer" title="{{item.value}}" ng-click="showText(item.value)"> <td width="30%" class="cursor-pointer" title="{{item.value}}"
ng-click="showText(item.value)">
<span ng-bind="item.value | limitTo: 250"></span> <span ng-bind="item.value | limitTo: 250"></span>
<span ng-bind="item.value.length > 250 ? '...': ''"></span> <span ng-bind="item.value.length > 250 ? '...': ''"></span>
</td> </td>
...@@ -298,11 +374,13 @@ ...@@ -298,11 +374,13 @@
<span ng-bind="item.newItem.key | limitTo: 250"></span> <span ng-bind="item.newItem.key | limitTo: 250"></span>
<span ng-bind="item.newItem.key.length > 250 ? '...' :''"></span> <span ng-bind="item.newItem.key.length > 250 ? '...' :''"></span>
</td> </td>
<td width="30%" class="cursor-pointer" title="{{item.oldItem.value}}" ng-click="showText(item.oldItem.value)"> <td width="30%" class="cursor-pointer" title="{{item.oldItem.value}}"
ng-click="showText(item.oldItem.value)">
<span ng-bind="item.oldItem.value | limitTo: 250"></span> <span ng-bind="item.oldItem.value | limitTo: 250"></span>
<span ng-bind="item.oldItem.value.length > 250 ? '...': ''"></span> <span ng-bind="item.oldItem.value.length > 250 ? '...': ''"></span>
</td> </td>
<td width="30%" class="cursor-pointer" title="{{item.newItem.value}}" ng-click="showText(item.newItem.value)"> <td width="30%" class="cursor-pointer" title="{{item.newItem.value}}"
ng-click="showText(item.newItem.value)">
<span ng-bind="item.newItem.value | limitTo: 250"></span> <span ng-bind="item.newItem.value | limitTo: 250"></span>
<span ng-bind="item.newItem.value.length > 250 ? '...': ''"></span> <span ng-bind="item.newItem.value.length > 250 ? '...': ''"></span>
</td> </td>
...@@ -337,13 +415,13 @@ ...@@ -337,13 +415,13 @@
<!--not properties format--> <!--not properties format-->
<div ng-if="!namespace.isPropertiesFormat"> <div ng-if="!namespace.isPropertiesFormat">
<div ng-repeat="item in commits.changeSets.createItems"> <div ng-repeat="item in commits.changeSets.createItems">
<textarea class="form-control" rows="20" style="border-radius: 0px" <textarea class="form-control no-radius" rows="20"
ng-disabled="true" ng-bind="item.value"> ng-disabled="true" ng-bind="item.value">
</textarea> </textarea>
</div> </div>
<div ng-repeat="item in commits.changeSets.updateItems"> <div ng-repeat="item in commits.changeSets.updateItems">
<textarea class="form-control" rows="20" style="border-radius: 0px" <textarea class="form-control no-radius" rows="20"
ng-disabled="true" ng-bind="item.newItem.value"> ng-disabled="true" ng-bind="item.newItem.value">
</textarea> </textarea>
</div> </div>
...@@ -359,7 +437,6 @@ ...@@ -359,7 +437,6 @@
<span class="glyphicon glyphicon-menu-down"></span></button> <span class="glyphicon glyphicon-menu-down"></span></button>
</div> </div>
</div> </div>
<!--instance view--> <!--instance view-->
<div class="panel panel-default instance-view" ng-show="namespace.viewType == 'instance'"> <div class="panel panel-default instance-view" ng-show="namespace.viewType == 'instance'">
<div class="panel-heading"> <div class="panel-heading">
...@@ -397,7 +474,7 @@ ...@@ -397,7 +474,7 @@
<div class="panel-default" ng-if="namespace.latestReleaseInstances.total > 0"> <div class="panel-default" ng-if="namespace.latestReleaseInstances.total > 0">
<div class="panel-heading"> <div class="panel-heading">
<a target="_blank" data-tooltip="tooltip" data-placement="bottom" title="查看配置" <a target="_blank" data-tooltip="tooltip" data-placement="bottom" title="查看配置"
href="/config/history.html?#/appid={{appId}}&env={{env}}&clusterName={{cluster}}&namespaceName={{namespace.baseInfo.namespaceName}}&scrollTo={{namespace.latestRelease.id}}"> href="/config/history.html?#/appid={{appId}}&env={{env}}&clusterName={{cluster}}&namespaceName={{namespace.baseInfo.namespaceName}}&releaseId={{namespace.latestRelease.id}}">
{{namespace.latestRelease.name}} {{namespace.latestRelease.name}}
</a> </a>
</div> </div>
...@@ -417,7 +494,9 @@ ...@@ -417,7 +494,9 @@
<td width="20%" ng-bind="instance.clusterName"></td> <td width="20%" ng-bind="instance.clusterName"></td>
<td width="20%" ng-bind="instance.dataCenter"></td> <td width="20%" ng-bind="instance.dataCenter"></td>
<td width="20%" ng-bind="instance.ip"></td> <td width="20%" ng-bind="instance.ip"></td>
<td width="20%">{{instance.configs && instance.configs.length ? (instance.configs[0].releaseDeliveryTime | date: 'yyyy-MM-dd HH:mm:ss') : ''}}</td> <td width="20%">{{instance.configs && instance.configs.length ?
(instance.configs[0].releaseDeliveryTime | date: 'yyyy-MM-dd HH:mm:ss') : ''}}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
...@@ -435,11 +514,12 @@ ...@@ -435,11 +514,12 @@
<!--not latest release instances--> <!--not latest release instances-->
<div class="panel-body" ng-show="namespace.instanceViewType == 'not_latest_release'"> <div class="panel-body" ng-show="namespace.instanceViewType == 'not_latest_release'">
<div class="panel-default" ng-if="namespace.instancesCount - namespace.latestReleaseInstances.total > 0" <div class="panel-default"
ng-if="namespace.instancesCount - namespace.latestReleaseInstances.total > 0"
ng-repeat="release in namespace.notLatestReleases"> ng-repeat="release in namespace.notLatestReleases">
<div class="panel-heading"> <div class="panel-heading">
<a target="_blank" data-tooltip="tooltip" data-placement="bottom" title="查看配置" <a target="_blank" data-tooltip="tooltip" data-placement="bottom" title="查看配置"
href="/config/history.html?#/appid={{appId}}&env={{env}}&clusterName={{cluster}}&namespaceName={{namespace.baseInfo.namespaceName}}&scrollTo={{release.id}}"> href="/config/history.html?#/appid={{appId}}&env={{env}}&clusterName={{cluster}}&namespaceName={{namespace.baseInfo.namespaceName}}&releaseId={{release.id}}">
{{release.name}} {{release.name}}
</a> </a>
...@@ -460,12 +540,15 @@ ...@@ -460,12 +540,15 @@
<td width="20%" ng-bind="instance.clusterName"></td> <td width="20%" ng-bind="instance.clusterName"></td>
<td width="20%" ng-bind="instance.dataCenter"></td> <td width="20%" ng-bind="instance.dataCenter"></td>
<td width="20%" ng-bind="instance.ip"></td> <td width="20%" ng-bind="instance.ip"></td>
<td width="20%">{{instance.configs && instance.configs.length ? (instance.configs[0].releaseDeliveryTime | date: 'yyyy-MM-dd HH:mm:ss') : ''}}</td> <td width="20%">{{instance.configs && instance.configs.length ?
(instance.configs[0].releaseDeliveryTime | date: 'yyyy-MM-dd HH:mm:ss') : ''}}
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="text-center" ng-if="namespace.instancesCount - namespace.latestReleaseInstances.total == 0"> <div class="text-center"
ng-if="namespace.instancesCount - namespace.latestReleaseInstances.total == 0">
无实例信息 无实例信息
</div> </div>
</div> </div>
...@@ -502,4 +585,517 @@ ...@@ -502,4 +585,517 @@
</div> </div>
</div> </section>
</section>
<!--branch panel body-->
<section class="branch-panel-body" ng-if="namespace.hasBranch && namespace.currentOperateBranch != 'master'">
<!--main header-->
<header class="panel-heading">
<div class="row">
<div class="col-md-6 header-namespace">
<b class="namespace-name" ng-bind="namespace.viewName" data-tooltip="tooltip"
data-placement="bottom"
title="{{namespace.comment}}"></b>
<span class="label label-info no-radius namespace-label" ng-bind="namespace.format"></span>
<span class="label label-warning no-radius namespace-label"
ng-show="namespace.branch.itemModifiedCnt > 0">有修改
<span class="badge label badge-white namespace-label"
ng-bind="namespace.branch.itemModifiedCnt"></span>
</span>
<span class="label label-primary no-radius namespace-label"
ng-show="namespace.branch.lockOwner">当前修改者:
<span ng-bind="namespace.branch.lockOwner"></span>
</span>
</div>
<div class="col-md-6 text-right header-buttons">
<a type="button" class="btn btn-success btn-sm"
data-tooltip="tooltip" data-placement="bottom" title="继续灰度发布"
ng-show="(namespace.hasReleasePermission || namespace.hasModifyPermission)"
ng-click="publish(namespace.branch)">
灰度发布
</a>
<a type="button" class="btn btn-primary btn-sm"
data-tooltip="tooltip" data-placement="bottom" title="合并到主版本并发布主版本配置"
ng-show="(namespace.hasReleasePermission || namespace.hasModifyPermission)"
ng-click="mergeAndPublish(namespace.branch)">
全量发布
</a>
<a type="button" class="btn btn-warning btn-sm"
data-tooltip="tooltip" data-placement="bottom" title="废弃灰度版本"
ng-show="(namespace.hasReleasePermission
|| (!namespace.branch.latestRelease && namespace.hasModifyPermission))"
ng-click="preDeleteBranch(namespace.branch)">
放弃灰度
</a>
</div>
</div>
</header>
<!--second header-->
<header class="panel-heading second-panel-heading">
<div class="row">
<div class="col-md-8 pull-left">
<ul class="nav nav-tabs">
<li role="presentation" ng-click="switchView(namespace.branch, 'table')"
ng-show="namespace.isPropertiesFormat">
<a ng-class="{node_active:namespace.branch.viewType == 'table'}">
<img src="img/table.png">
配置
</a>
</li>
<li role="presentation" ng-click="switchView(namespace.branch, 'rule')">
<a ng-class="{node_active:namespace.branch.viewType == 'rule'}">
<img src="img/rule.png">
灰度规则
<span class="badge badge-grey"
ng-bind="namespace.branch.grayIps.length + namespace.branch.grayApps.length"></span>
</a>
</li>
<li role="presentation" ng-click="switchView(namespace.branch, 'instance')">
<a ng-class="{node_active:namespace.branch.viewType == 'instance'}">
<img src="img/machine.png">
灰度实例列表
<span class="badge badge-grey"
ng-bind="namespace.branch.latestReleaseInstances.total"></span>
</a>
</li>
<li role="presentation" ng-click="switchView(namespace.branch, 'history')">
<a ng-class="{node_active:namespace.branch.viewType == 'history'}">
<img src="img/change.png">
更改历史
</a>
</li>
</ul>
</div>
</div>
</header>
<section>
<!--items-->
<div class="namespace-view-table" ng-show="namespace.branch.viewType == 'table'">
<div class="panel panel-default" ng-if="namespace.hasBranch">
<div class="panel-heading">
灰度的配置
<button type="button" class="btn btn-primary btn-sm pull-right" style="margin-top: -4px;"
ng-show="namespace.hasModifyPermission"
ng-click="createItem(namespace.branch)">
<img src="img/plus.png">
新增灰度配置
</button>
</div>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>发布状态</th>
<th class="hover" title="排序"
ng-click="col='item.key';desc=!desc;">
Key&nbsp;
<span class="glyphicon glyphicon-sort"></span>
</th>
<th>
主版本的值
</th>
<th>
灰度的值
</th>
<th>
备注
</th>
<th class="hover" title="排序"
ng-click="col='item.dataChangeLastModifiedBy';desc=!desc;">
最后修改人
<span class="glyphicon glyphicon-sort"></span>
</th>
<th class="hover" title="排序"
ng-click="col='item.dataChangeLastModifiedTime';desc=!desc;">
最后修改时间
<span class="glyphicon glyphicon-sort"></span>
</th>
<th>
操作
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="config in namespace.branch.branchItems |orderBy:col:desc"
ng-if="config.item.key">
<td width="7%" class="text-center">
<span class="label label-warning no-radius cursor-pointer"
data-tooltip="tooltip" data-placement="bottom" title="点击查看已发布的值"
ng-if="config.isModified || config.isDeleted"
ng-click="showText(config.oldValue)">未发布</span>
<span class="label label-default-light no-radius"
data-tooltip="tooltip" data-placement="bottom" title="已生效的配置"
ng-if="!config.isModified">已发布</span>
</td>
<td width="15%" class="cursor-pointer" title="点击查看" ng-click="showText(config.item.key)">
<span ng-bind="config.item.key | limitTo: 250"></span>
<span ng-bind="config.item.key.length > 250 ? '...' :''"></span>
<span class="label label-danger" ng-if="config.isDeleted"
data-tooltip="tooltip" data-placement="bottom" title="删除的配置"></span>
<span class="label label-info" ng-if="!config.isDeleted && config.masterReleaseValue"
data-tooltip="tooltip" data-placement="bottom" title="修改主版本的配置"></span>
<span class="label label-success"
ng-if="!config.isDeleted && !config.masterReleaseValue"
data-tooltip="tooltip" data-placement="bottom" title="灰度版本特有的配置"></span>
</td>
<td width="20%" class="cursor-pointer" title="点击查看"
ng-click="showText(config.masterReleaseValue)">
<span ng-bind="config.masterReleaseValue | limitTo: 250"></span>
<span ng-bind="config.item.value.length > 250 ? '...': ''"></span>
</td>
<td width="20%" class="cursor-pointer" title="点击查看" ng-click="showText(config.item.value)">
<span ng-bind="config.item.value | limitTo: 250"></span>
<span ng-bind="config.item.value.length > 250 ? '...': ''"></span>
</td>
<td width="10%" title="{{config.item.comment}}">
<span ng-bind="config.item.comment | limitTo: 250"></span>
<span ng-bind="config.item.comment.length > 250 ?'...' : ''"></span>
</td>
<td width="10%" ng-bind="config.item.dataChangeLastModifiedBy">
</td>
<td width="10%"
ng-bind="config.item.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'">
</td>
<td width="9%" class="text-center">
<img src="img/edit.png"
data-tooltip="tooltip" data-placement="bottom" title="修改"
ng-if="!config.isDeleted"
ng-click="editItem(namespace.branch, config.item)"
ng-show="namespace.hasModifyPermission">
<img style="margin-left: 5px;" src="img/cancel.png"
data-tooltip="tooltip" data-placement="bottom" title="删除"
ng-if="!config.isDeleted"
ng-click="preDeleteItem(namespace.branch, config.item.id)"
ng-show="namespace.hasModifyPermission">
</td>
</tr>
</tbody>
</table>
</div>
<div class="panel panel-default"
ng-if="namespace.branch.masterItems && namespace.branch.masterItems.length > 0">
<div class="panel-heading">
主版本的配置
</div>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>发布状态</th>
<th class="hover" title="排序"
ng-click="col='item.key';desc=!desc;">
Key&nbsp;
<span class="glyphicon glyphicon-sort"></span>
</th>
<th>
Value
</th>
<th>
备注
</th>
<th class="hover" title="排序"
ng-click="col='item.dataChangeLastModifiedBy';desc=!desc;">
最后修改人
<span class="glyphicon glyphicon-sort"></span>
</th>
<th class="hover" title="排序"
ng-click="col='item.dataChangeLastModifiedTime';desc=!desc;">
最后修改时间
<span class="glyphicon glyphicon-sort"></span>
</th>
<th>
操作
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="config in namespace.branch.masterItems |orderBy:col:desc"
ng-if="config.item.key">
<td width="8%" class="text-center">
<span class="label label-warning no-radius cursor-pointer"
data-tooltip="tooltip" data-placement="bottom" title="点击查看已发布的值"
ng-if="config.isModified || config.isDeleted"
ng-click="showText(config.oldValue)">未发布</span>
<span class="label label-default-light no-radius"
data-tooltip="tooltip" data-placement="bottom" title="已生效的配置"
ng-if="!config.isModified">已发布</span>
</td>
<td width="15%" class="cursor-pointer" title="点击查看" ng-click="showText(config.item.key)">
<span ng-bind="config.item.key | limitTo: 250"></span>
<span ng-bind="config.item.key.length > 250 ? '...' :''"></span>
<span class="label label-success" ng-if="config.isModified && !config.oldValue"
data-tooltip="tooltip" data-placement="bottom" title="新增的配置"></span>
<span class="label label-info"
ng-if="config.isModified && config.oldValue && !config.isDeleted"
data-tooltip="tooltip" data-placement="bottom" title="修改的配置"></span>
<span class="label label-danger" ng-if="config.isDeleted"
data-tooltip="tooltip" data-placement="bottom" title="删除的配置"></span>
</td>
<td width="35%" class="cursor-pointer" title="点击查看" ng-click="showText(config.item.value)">
<span ng-bind="config.item.value | limitTo: 250"></span>
<span ng-bind="config.item.value.length > 250 ? '...': ''"></span>
</td>
<td width="15%" title="{{config.item.comment}}">
<span ng-bind="config.item.comment | limitTo: 250"></span>
<span ng-bind="config.item.comment.length > 250 ?'...' : ''"></span>
</td>
<td width="10%" ng-bind="config.item.dataChangeLastModifiedBy">
</td>
<td width="12%"
ng-bind="config.item.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'">
</td>
<td width="5%" class="text-center">
<img src="img/gray.png"
data-tooltip="tooltip" data-placement="bottom" title="对此配置灰度"
ng-if="!config.isDeleted"
ng-click="editItem(namespace.branch, config.item)"
ng-show="namespace.hasModifyPermission">
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!--gray rules-->
<div class="rules-manage-view row" ng-show="namespace.branch.viewType == 'rule'">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>灰度的AppId</th>
<th>灰度的IP列表</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="ruleItem in namespace.branch.rules.ruleItems">
<td width="20%" ng-bind="ruleItem.clientAppId"></td>
<td width="70%" ng-show="!ruleItem.ApplyToAllInstances"
ng-bind="ruleItem.clientIpList.join(', ')"></td>
<td width="70%" ng-show="ruleItem.ApplyToAllInstances">ALL</td>
<td class="text-center" width="10%">
<img src="img/edit.png" class="i-20 hover"
data-tooltip="tooltip" data-placement="bottom" title="修改"
ng-click="editRuleItem(namespace.branch, ruleItem)">
<img src="img/cancel.png" class="i-20 hover" style="margin-left: 5px;"
data-tooltip="tooltip" data-placement="bottom" title="删除"
ng-click="deleteRuleItem(namespace.branch, ruleItem)">
</td>
</tr>
</tbody>
</table>
<button class="btn btn-primary"
ng-show="(namespace.isPublic && !namespace.isLinkedNamespace) ||
((!namespace.isPublic || namespace.isLinkedNamespace)
&& (!namespace.branch.rules
|| !namespace.branch.rules.ruleItems
|| !namespace.branch.rules.ruleItems.length))"
ng-click="addRuleItem(namespace.branch)">新增规则
</button>
</div>
<!--instances -->
<div class="panel panel-default" ng-show="namespace.branch.viewType == 'instance'">
<div class="panel-heading text-right">
<button class="btn btn-default btn-sm"
data-tooltip="tooltip" data-placement="bottom" title="刷新列表"
ng-click="refreshInstancesInfo(namespace.branch)">
<img src="../../img/refresh.png"/>
</button>
</div>
<div class="panel-body">
<div class="panel-default" ng-if="namespace.branch.latestReleaseInstances.total > 0">
<div class="panel-heading">
<a target="_blank" data-tooltip="tooltip" data-placement="bottom" title="查看配置"
href="/config/history.html?#/appid={{appId}}&env={{env}}&clusterName={{namespace.baseInfo.clusterName}}&namespaceName={{namespace.baseInfo.namespaceName}}&releaseId={{namespace.branch.latestRelease.id}}">
{{namespace.branch.latestRelease.name}}
</a>
</div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<td>App ID</td>
<td>Cluster Name</td>
<td>Data Center</td>
<td>IP</td>
<td>配置获取时间</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="instance in namespace.branch.latestReleaseInstances.content">
<td width="20%" ng-bind="instance.appId"></td>
<td width="20%" ng-bind="instance.clusterName"></td>
<td width="20%" ng-bind="instance.dataCenter"></td>
<td width="20%" ng-bind="instance.ip"></td>
<td width="20%">{{instance.configs && instance.configs.length ?
(instance.configs[0].releaseDeliveryTime | date: 'yyyy-MM-dd HH:mm:ss') : ''}}
</td>
</tr>
</tbody>
</table>
<div class="row text-center"
ng-show="namespace.branch.latestReleaseInstances.content.length < namespace.branch.latestReleaseInstances.total">
<button class="btn btn-default" ng-click="loadInstanceInfo(namespace.branch)">加载更多</button>
</div>
</div>
<div class="text-center" ng-if="namespace.branch.latestReleaseInstances.total == 0">
无实例信息
</div>
</div>
</div>
<!--history view-->
<div class="J_historyview history-view" ng-show="namespace.branch.viewType == 'history'">
<div class="media" ng-repeat="commits in namespace.branch.commits">
<div class="media-body">
<div class="row">
<div class="col-md-6"><h3 class="media-heading"
ng-bind="commits.dataChangeCreatedBy"></h3>
</div>
<div class="col-md-6 text-right">
<h5 class="media-heading"
ng-bind="commits.dataChangeCreatedTime | date: 'yyyy-MM-dd HH:mm:ss'"></h5>
</div>
</div>
<!--properties format-->
<table class="table table-bordered table-striped text-center table-hover"
style="margin-top: 5px;"
ng-if="namespace.isPropertiesFormat">
<thead>
<tr>
<th>
Type
</th>
<th>
Key
</th>
<th>
Old Value
</th>
<th>
New Value
</th>
<th>
Comment
</th>
</tr>
</thead>
<tbody>
<!--兼容老数据,不显示item类型为空行和注释的item-->
<tr ng-repeat="item in commits.changeSets.createItems" ng-show="item.key">
<td width="2%">
新增
</td>
<td width="20%" title="{{item.key}}">
<span ng-bind="item.key | limitTo: 250"></span>
<span ng-bind="item.key.length > 250 ? '...' :''"></span>
</td>
<td width="30%">
</td>
<td width="30%" class="cursor-pointer" title="{{item.value}}"
ng-click="showText(item.value)">
<span ng-bind="item.value | limitTo: 250"></span>
<span ng-bind="item.value.length > 250 ? '...': ''"></span>
</td>
<td width="18%" title="{{item.comment}}">
<span ng-bind="item.comment | limitTo: 250"></span>
<span ng-bind="item.comment.length > 250 ?'...' : ''"></span>
</td>
</tr>
<tr ng-repeat="item in commits.changeSets.updateItems">
<td width="2%">
更新
</td>
<td width="20%" title="{{item.newItem.key}}">
<span ng-bind="item.newItem.key | limitTo: 250"></span>
<span ng-bind="item.newItem.key.length > 250 ? '...' :''"></span>
</td>
<td width="30%" class="cursor-pointer" title="{{item.oldItem.value}}"
ng-click="showText(item.oldItem.value)">
<span ng-bind="item.oldItem.value | limitTo: 250"></span>
<span ng-bind="item.oldItem.value.length > 250 ? '...': ''"></span>
</td>
<td width="30%" class="cursor-pointer" title="{{item.newItem.value}}"
ng-click="showText(item.newItem.value)">
<span ng-bind="item.newItem.value | limitTo: 250"></span>
<span ng-bind="item.newItem.value.length > 250 ? '...': ''"></span>
</td>
<td width="18%" title="{{item.newItem.comment}}">
<span ng-bind="item.newItem.comment | limitTo: 250"></span>
<span ng-bind="item.newItem.comment.length > 250 ?'...' : ''"></span>
</td>
</tr>
<tr ng-repeat="item in commits.changeSets.deleteItems"
ng-show="item.key || item.comment">
<td width="2%">
删除
</td>
<td width="20%" title="{{item.key}}">
<span ng-bind="item.key | limitTo: 250"></span>
<span ng-bind="item.key.length > 250 ? '...' :''"></span>
</td>
<td width="30%" title="{{item.value}}">
<span ng-bind="item.value | limitTo: 250"></span>
<span ng-bind="item.value.length > 250 ? '...': ''"></span>
</td>
<td width="30%">
</td>
<td width="18%" title="{{item.comment}}">
<span ng-bind="item.comment | limitTo: 250"></span>
<span ng-bind="item.comment.length > 250 ?'...' : ''"></span>
</td>
</tr>
</tbody>
</table>
<!--not properties format-->
<div ng-if="!namespace.isPropertiesFormat">
<div ng-repeat="item in commits.changeSets.createItems">
<textarea class="form-control no-radius" rows="20"
ng-disabled="true" ng-bind="item.value">
</textarea>
</div>
<div ng-repeat="item in commits.changeSets.updateItems">
<textarea class="form-control no-radius" rows="20"
ng-disabled="true" ng-bind="item.newItem.value">
</textarea>
</div>
</div>
</div>
<hr>
</div>
<div class="text-center">
<button type="button" class="btn btn-default" ng-show="!namespace.branch.hasLoadAllCommit"
ng-click="loadCommitHistory(namespace.branch)">加载更多
<span class="glyphicon glyphicon-menu-down"></span></button>
</div>
</div>
</section>
</section>
</section>
<form id="releaseModal" class="modal fade form-horizontal" name="releaseForm" valdr-type="Release"
ng-submit="release()">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title" ng-show="!toReleaseNamespace.isBranch">发布</h4>
<h4 class="modal-title" ng-show="toReleaseNamespace.isBranch && !toReleaseNamespace.mergeAndPublish">
灰度发布</h4>
<h4 class="modal-title" ng-show="toReleaseNamespace.isBranch && toReleaseNamespace.mergeAndPublish">
全量发布</h4>
</div>
<div class="release modal-body">
<div class="form-group">
<div class="col-sm-2 control-label" ng-if="!toReleaseNamespace.isPropertiesFormat">
<div class="row">
<div class="btn-group btn-group-xs" style="padding-right: 10px" role="group">
<button type="button" class="btn btn-default"
ng-class="{active:releaseChangeViewType=='change'}"
ng-click="switchReleaseChangeViewType('change')">查看变更
</button>
<button type="button" class="btn btn-default"
ng-class="{active:releaseChangeViewType=='release'}"
ng-click="switchReleaseChangeViewType('release')">发布的值
</button>
</div>
</div>
</div>
<label class="col-sm-2 control-label" ng-if="toReleaseNamespace.isPropertiesFormat">Changes</label>
<div class="col-sm-10"
ng-if="(!toReleaseNamespace.isBranch && toReleaseNamespace.itemModifiedCnt)
|| (toReleaseNamespace.isBranch && toReleaseNamespace.itemModifiedCnt)
|| (toReleaseNamespace.isBranch && toReleaseNamespace.mergeAndPublish && toReleaseNamespace.branchItems.length)"
valdr-form-group>
<!--properties format-->
<!--normal release-->
<table class="table table-bordered table-striped text-center table-hover"
ng-if="toReleaseNamespace.isPropertiesFormat && !toReleaseNamespace.isBranch">
<thead>
<tr>
<th>
Key
</th>
<th>
发布的值
</th>
<th>
未发布的值
</th>
<th>
修改人
</th>
<th>
修改时间
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="config in toReleaseNamespace.items"
ng-if="config.item.key && config.isModified">
<td width="20%" title="{{config.item.key}}">
<span ng-bind="config.item.key"></span>
<span class="label label-success" ng-if="config.isModified && !config.oldValue"
data-tooltip="tooltip" data-placement="bottom" title="新增的配置"></span>
<span class="label label-info"
ng-if="config.isModified && config.oldValue && !config.isDeleted"
data-tooltip="tooltip" data-placement="bottom" title="修改的配置"></span>
<span class="label label-danger" ng-if="config.isDeleted"
data-tooltip="tooltip" data-placement="bottom" title="删除的配置"></span>
</td>
<td width="25%" title="{{config.oldValue}}">
<span ng-bind="config.oldValue"></span>
</td>
<td width="25%" title="{{config.newValue}}">
<span ng-bind="config.newValue"></span>
</td>
<td width="15%" ng-bind="config.item.dataChangeLastModifiedBy">
</td>
<td width="15%"
ng-bind="config.item.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'">
</td>
</tr>
</tbody>
</table>
<!--branch gray release-->
<table class="table table-bordered table-striped text-center table-hover"
ng-if="toReleaseNamespace.isPropertiesFormat &&
toReleaseNamespace.isBranch && !toReleaseNamespace.mergeAndPublish">
<thead>
<tr>
<th>
Key
</th>
<th>
主版本值
</th>
<th>
灰度版本发布的值
</th>
<th>
灰度版本未发布的值
</th>
<th>
修改人
</th>
<th>
修改时间
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="config in toReleaseNamespace.branchItems"
ng-if="config.isModified || config.isDeleted">
<td width="15%" title="{{config.item.key}}">
<span ng-bind="config.item.key"></span>
<span class="label label-danger"
ng-show="config.isDeleted"></span>
</td>
<td width="20%" title="{{config.masterReleaseValue}}">
<span ng-bind="config.masterReleaseValue"></span>
</td>
<td width="20%" title="{{config.oldValue}}">
<span ng-bind="config.oldValue"></span>
</td>
<td width="20%" title="{{config.newValue}}">
<span ng-bind="config.newValue"></span>
</td>
<td width="10%" ng-bind="config.item.dataChangeLastModifiedBy">
</td>
<td width="15%"
ng-bind="config.item.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'">
</td>
</tr>
</tbody>
</table>
<!--branch updateAndPublish and publish-->
<table class="table table-bordered table-striped text-center table-hover"
ng-if="toReleaseNamespace.isPropertiesFormat &&
toReleaseNamespace.isBranch && toReleaseNamespace.mergeAndPublish">
<thead>
<tr>
<th>
Key
</th>
<th ng-if="toReleaseNamespace.isBranch">
主版本值
</th>
<th ng-if="toReleaseNamespace.isBranch && toReleaseNamespace.mergeAndPublish">
灰度版本的值
</th>
<th ng-if="!toReleaseNamespace.isBranch || !toReleaseNamespace.mergeAndPublish">
发布的值
</th>
<th ng-if="!toReleaseNamespace.isBranch || !toReleaseNamespace.mergeAndPublish">
未发布的值
</th>
<th>
修改人
</th>
<th>
修改时间
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="config in toReleaseNamespace.branchItems"
ng-if="!config.isDeleted">
<td width="20%" title="{{config.item.key}}">
<span ng-bind="config.item.key"></span>
</td>
<td width="25%" title="{{config.masterReleaseValue}}">
<span ng-bind="config.masterReleaseValue"></span>
</td>
<td width="25%" title="{{config.item.value}}">
<span ng-bind="config.item.value"></span>
</td>
<td width="15%" ng-bind="config.item.dataChangeLastModifiedBy">
</td>
<td width="15%"
ng-bind="config.item.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'">
</td>
</tr>
</tbody>
</table>
<!--file format -->
<div ng-repeat="item in toReleaseNamespace.items"
ng-if="!toReleaseNamespace.isPropertiesFormat"
ng-show="releaseChangeViewType=='change'">
<apollodiff old-str="item.oldValue" new-str="item.newValue"
apollo-id="'releaseStrDiff'"></apollodiff>
</div>
<div ng-repeat="item in toReleaseNamespace.items"
ng-if="!toReleaseNamespace.isPropertiesFormat"
ng-show="releaseChangeViewType=='release'">
<textarea class="form-control no-radius" rows="20"
ng-disabled="true" ng-show="item.newValue" ng-bind="item.newValue">
</textarea>
</div>
</div>
<div class="col-sm-5"
ng-show="(!toReleaseNamespace.isBranch && !toReleaseNamespace.itemModifiedCnt)"
valdr-form-group>
<label class="form-control-static">
配置没有变化
</label>
</div>
<div class="col-sm-5"
ng-show="(toReleaseNamespace.isBranch && !toReleaseNamespace.mergeAndPublish && !toReleaseNamespace.itemModifiedCnt)"
valdr-form-group>
<label class="form-control-static">
灰度配置没有变化
</label>
</div>
<div class="col-sm-5"
ng-show="(toReleaseNamespace.isBranch && toReleaseNamespace.mergeAndPublish && toReleaseNamespace.branchItems.length == 0)"
valdr-form-group>
<label class="form-control-static">
没有灰度的配置项
</label>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">
<apollorequiredfield></apollorequiredfield>
Release Name</label>
<div class="col-sm-5" valdr-form-group>
<input type="text" name="releaseName" class="form-control"
placeholder="input release name"
ng-model="toReleaseNamespace.releaseTitle" ng-required="true">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Comment</label>
<div class="col-sm-10" valdr-form-group>
<textarea rows="4" name="comment" class="form-control"
style="margin-top: 15px;"
ng-model="releaseComment"
placeholder="Add an optional extended description..."></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="submit" class="btn btn-primary"
ng-disabled="releaseForm.$invalid || releaseBtnDisabled
|| (toReleaseNamespace.isBranch && toReleaseNamespace.mergeAndPublish && toReleaseNamespace.branchItems.length == 0)">
发布
</button>
</div>
</div>
</div>
</form>
<form id="rollbackModal" class="modal fade form-horizontal"
ng-submit="showRollbackAlertDialog()">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<div class="modal-title text-center">
<span style="font-size: 18px;" ng-bind="toRollbackNamespace.firstRelease.name"></span>
<span style="font-size: 18px;"> &nbsp;回滚到&nbsp;</span>
<span style="font-size: 18px;" ng-bind="toRollbackNamespace.secondRelease.name"></span>
</div>
</div>
<div class="modal-body">
<div class="alert alert-warning" role="alert">
此操作将会回滚到上一个发布版本,且当前版本作废,但不影响正在修改的配置。可在发布历史页面查看当前生效的版本
<a target="_blank"
href="/config/history.html?#/appid={{appId}}&env={{env}}&clusterName={{toRollbackNamespace.baseInfo.clusterName}}&namespaceName={{toRollbackNamespace.baseInfo.namespaceName}}">点击查看</a>
</div>
<div class="form-group" style="margin-top: 15px;">
<!--properties format-->
<div class="col-sm-12"
ng-if="toRollbackNamespace.releaseCompareResult.length > 0 && toRollbackNamespace.isPropertiesFormat">
<table class="table table-bordered table-striped text-center table-hover"
ng-if="toRollbackNamespace.isPropertiesFormat">
<thead>
<tr>
<th>
Type
</th>
<th>
Key
</th>
<th>
回滚前
</th>
<th>
回滚后
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="change in toRollbackNamespace.releaseCompareResult">
<td width="10%">
<span ng-show="change.type == 'ADDED'">新增</span>
<span ng-show="change.type == 'MODIFIED'">更新</span>
<span ng-show="change.type == 'DELETED'">删除</span>
</td>
<td width="20%" ng-bind="change.entity.firstEntity.key">
</td>
<td width="35%" ng-bind="change.entity.firstEntity.value">
</td>
<td width="35%" ng-bind="change.entity.secondEntity.value">
</td>
</tr>
</tbody>
</table>
</div>
<!--file format -->
<div class="col-sm-12"
ng-if="toRollbackNamespace.releaseCompareResult.length > 0 && !toRollbackNamespace.isPropertiesFormat">
<div ng-repeat="change in toRollbackNamespace.releaseCompareResult"
ng-if="!toRollbackNamespace.isPropertiesFormat">
<h5>回滚前</h5>
<textarea class="form-control no-radius" rows="20"
ng-disabled="true" ng-bind="change.entity.firstEntity.value">
</textarea>
<hr>
<h5>回滚后</h5>
<textarea class="form-control no-radius" rows="20"
ng-disabled="true" ng-bind="change.entity.secondEntity.value">
</textarea>
</div>
</div>
<div class="col-sm-12 text-center" ng-if="toRollbackNamespace.releaseCompareResult.length == 0">
<h4>
配置没有变化
</h4>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="submit" class="btn btn-danger"
ng-disabled="toRollbackNamespace.rollbackBtnDisabled">回滚
</button>
</div>
</div>
</div>
</form>
<div id="showTextModal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content no-radius">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">查看</h4>
</div>
<pre id="watchText" class="modal-body no-radius" style="margin-bottom: 0"
ng-bind="text">
</pre>
</div>
</div>
</div>
...@@ -11,7 +11,7 @@ import com.ctrip.framework.apollo.portal.auth.UserInfoHolder; ...@@ -11,7 +11,7 @@ import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
import com.ctrip.framework.apollo.portal.entity.po.UserInfo; import com.ctrip.framework.apollo.portal.entity.po.UserInfo;
import com.ctrip.framework.apollo.portal.entity.vo.ItemDiffs; import com.ctrip.framework.apollo.portal.entity.vo.ItemDiffs;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier; import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier;
import com.ctrip.framework.apollo.portal.entity.form.NamespaceTextModel; import com.ctrip.framework.apollo.portal.entity.model.NamespaceTextModel;
import com.ctrip.framework.apollo.portal.service.txtresolver.PropertyResolver; import com.ctrip.framework.apollo.portal.service.txtresolver.PropertyResolver;
import org.junit.Assert; import org.junit.Assert;
......
...@@ -139,7 +139,7 @@ ...@@ -139,7 +139,7 @@
<dependency> <dependency>
<groupId>com.ctrip.framework</groupId> <groupId>com.ctrip.framework</groupId>
<artifactId>framework-foundation</artifactId> <artifactId>framework-foundation</artifactId>
<version>1.2.1</version> <version>1.4.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.dianping.cat</groupId> <groupId>com.dianping.cat</groupId>
......
...@@ -93,6 +93,7 @@ CREATE TABLE `Cluster` ( ...@@ -93,6 +93,7 @@ CREATE TABLE `Cluster` (
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键', `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`Name` varchar(32) NOT NULL DEFAULT '' COMMENT '集群名字', `Name` varchar(32) NOT NULL DEFAULT '' COMMENT '集群名字',
`AppId` varchar(32) NOT NULL DEFAULT '' COMMENT 'App id', `AppId` varchar(32) NOT NULL DEFAULT '' COMMENT 'App id',
`ParentClusterId` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '父cluster',
`IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal', `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
`DataChange_CreatedBy` varchar(32) NOT NULL DEFAULT '' COMMENT '创建人邮箱前缀', `DataChange_CreatedBy` varchar(32) NOT NULL DEFAULT '' COMMENT '创建人邮箱前缀',
`DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
...@@ -129,6 +130,29 @@ CREATE TABLE `Commit` ( ...@@ -129,6 +130,29 @@ CREATE TABLE `Commit` (
KEY `NamespaceName` (`NamespaceName`(191)) KEY `NamespaceName` (`NamespaceName`(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='commit 历史表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='commit 历史表';
# Dump of table grayreleaserule
# ------------------------------------------------------------
DROP TABLE IF EXISTS `GrayReleaseRule`;
CREATE TABLE `GrayReleaseRule` (
`Id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`AppId` varchar(32) NOT NULL DEFAULT 'default' COMMENT 'AppID',
`ClusterName` varchar(32) NOT NULL DEFAULT 'default' COMMENT 'Cluster Name',
`NamespaceName` varchar(32) NOT NULL DEFAULT 'default' COMMENT 'Namespace Name',
`BranchName` varchar(32) NOT NULL DEFAULT 'default' COMMENT 'branch name',
`Rules` varchar(16000) DEFAULT '[]' COMMENT '灰度规则',
`ReleaseId` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '灰度对应的release',
`BranchStatus` tinyint(2) DEFAULT '1' COMMENT '灰度分支状态: 0:删除分支,1:正在使用的规则 2:全量发布',
`IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
`DataChange_CreatedBy` varchar(32) NOT NULL DEFAULT 'default' COMMENT '创建人邮箱前缀',
`DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`DataChange_LastModifiedBy` varchar(32) DEFAULT '' COMMENT '最后修改人邮箱前缀',
`DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
PRIMARY KEY (`Id`),
KEY `DataChange_LastTime` (`DataChange_LastTime`),
KEY `IX_Namespace` (`AppId`,`ClusterName`,`NamespaceName`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='灰度规则表';
# Dump of table instance # Dump of table instance
...@@ -168,7 +192,7 @@ CREATE TABLE `InstanceConfig` ( ...@@ -168,7 +192,7 @@ CREATE TABLE `InstanceConfig` (
`DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`DataChange_LastTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', `DataChange_LastTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
PRIMARY KEY (`Id`), PRIMARY KEY (`Id`),
UNIQUE KEY `IX_UNIQUE_KEY` (`InstanceId`,`ConfigAppId`,`ConfigClusterName`,`ConfigNamespaceName`), UNIQUE KEY `IX_UNIQUE_KEY` (`InstanceId`,`ConfigAppId`,`ConfigNamespaceName`),
KEY `IX_ReleaseKey` (`ReleaseKey`), KEY `IX_ReleaseKey` (`ReleaseKey`),
KEY `IX_DataChange_LastTime` (`DataChange_LastTime`), KEY `IX_DataChange_LastTime` (`DataChange_LastTime`),
KEY `IX_Valid_Namespace` (`ConfigAppId`,`ConfigClusterName`,`ConfigNamespaceName`,`DataChange_LastTime`) KEY `IX_Valid_Namespace` (`ConfigAppId`,`ConfigClusterName`,`ConfigNamespaceName`,`DataChange_LastTime`)
...@@ -292,6 +316,32 @@ CREATE TABLE `Release` ( ...@@ -292,6 +316,32 @@ CREATE TABLE `Release` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='发布'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='发布';
# Dump of table releasehistory
# ------------------------------------------------------------
DROP TABLE IF EXISTS `ReleaseHistory`;
CREATE TABLE `ReleaseHistory` (
`Id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增Id',
`AppId` varchar(32) NOT NULL DEFAULT 'default' COMMENT 'AppID',
`ClusterName` varchar(32) NOT NULL DEFAULT 'default' COMMENT 'ClusterName',
`NamespaceName` varchar(32) NOT NULL DEFAULT 'default' COMMENT 'namespaceName',
`BranchName` varchar(32) NOT NULL DEFAULT 'default' COMMENT '发布分支名',
`ReleaseId` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '关联的Release Id',
`PreviousReleaseId` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '前一次发布的ReleaseId',
`Operation` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '发布类型,0: 普通发布,1: 回滚,2: 灰度发布,3: 灰度规则更新,4: 灰度合并回主分支发布,5: 主分支发布灰度自动发布,6: 主分支回滚灰度自动发布,7: 放弃灰度',
`OperationContext` longtext NOT NULL COMMENT '发布上下文信息',
`IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
`DataChange_CreatedBy` varchar(32) NOT NULL DEFAULT 'default' COMMENT '创建人邮箱前缀',
`DataChange_CreatedTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`DataChange_LastModifiedBy` varchar(32) DEFAULT '' COMMENT '最后修改人邮箱前缀',
`DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
PRIMARY KEY (`Id`),
KEY `IX_Namespace` (`AppId`,`ClusterName`,`NamespaceName`,`BranchName`),
KEY `IX_ReleaseId` (`ReleaseId`),
KEY `IX_DataChange_LastTime` (`DataChange_LastTime`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='发布历史';
# Dump of table releasemessage # Dump of table releasemessage
# ------------------------------------------------------------ # ------------------------------------------------------------
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment