Skip to content

Commit 612af88

Browse files
implementing registerIacTemplate API
1 parent 161da5d commit 612af88

6 files changed

Lines changed: 99 additions & 46 deletions

File tree

plugins/iac/nimble/src/main/java/org/apache/cloudstack/api/command/RegisterIacTemplateCmd.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@
2121
import org.apache.cloudstack.api.APICommand;
2222
import org.apache.cloudstack.api.ApiArgValidator;
2323
import org.apache.cloudstack.api.ApiConstants;
24-
import org.apache.cloudstack.api.ApiErrorCode;
2524
import org.apache.cloudstack.api.BaseCmd;
2625
import org.apache.cloudstack.api.Parameter;
27-
import org.apache.cloudstack.api.ServerApiException;
2826
import org.apache.cloudstack.api.response.AccountResponse;
2927
import org.apache.cloudstack.api.response.DomainResponse;
3028
import org.apache.cloudstack.api.response.IacTemplateResponse;
@@ -34,6 +32,7 @@
3432
import org.apache.cloudstack.service.NimbleService;
3533

3634
import javax.inject.Inject;
35+
import java.util.ArrayList;
3736
import java.util.List;
3837

3938
@APICommand(name = "registerIacTemplate",
@@ -51,19 +50,19 @@ public class RegisterIacTemplateCmd extends BaseCmd {
5150
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "Description of the IaC template.")
5251
private String description;
5352

54-
@Parameter(name = ApiConstants.IAC_TEMPLATE_CONTENT, type = CommandType.STRING, length = 65535,
53+
@Parameter(name = ApiConstants.IAC_TEMPLATE_CONTENT, type = CommandType.STRING, length = 65535,
5554
description = "Content of the IaC template.", required = true, validations = {ApiArgValidator.NotNullOrEmpty})
5655
private String iacTemplateContent;
5756

58-
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class,
57+
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class,
5958
description = "ID of the domain associated with the IaC template. It must be used along with the \"account\" parameter.")
6059
private Long domainId;
6160

62-
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING,
61+
@Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING,
6362
description = "Name of the account that will own the IaC template. It must be used along with the \"domainid\" parameter.")
6463
private String accountName;
6564

66-
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class,
65+
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class,
6766
description = "ID of the project that will own the IaC template. Mutually exclusive with the \"account\" parameter.")
6867
private Long projectId;
6968

@@ -107,15 +106,28 @@ public Long getProjectId() {
107106
return projectId;
108107
}
109108

109+
public boolean isTemplateShared() {
110+
return sharedDomainIds != null || sharedAccountIds != null || sharedProjectIds != null;
111+
}
112+
110113
public List<Long> getSharedDomainIds() {
114+
if (sharedDomainIds == null) {
115+
return new ArrayList<>();
116+
}
111117
return sharedDomainIds;
112118
}
113119

114120
public List<Long> getSharedAccountIds() {
121+
if (sharedAccountIds == null) {
122+
return new ArrayList<>();
123+
}
115124
return sharedAccountIds;
116125
}
117126

118127
public List<Long> getSharedProjectIds() {
128+
if (sharedProjectIds == null) {
129+
return new ArrayList<>();
130+
}
119131
return sharedProjectIds;
120132
}
121133

plugins/iac/nimble/src/main/java/org/apache/cloudstack/api/response/NimbleResponseBuilder.java

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public IacTemplateResponse createIacTemplateResponse(IacTemplate iacTemplate, bo
7373
Account owner = ApiDBUtils.findAccountById(iacTemplate.getAccountId());
7474
populateIacTemplateOwnerFields(response, caller, owner);
7575
populateIacTemplateSharedEntitiesFields(response, iacTemplate, caller, owner);
76+
7677
response.setObjectName("iactemplates");
7778
return response;
7879
}
@@ -95,36 +96,32 @@ private void populateIacTemplateOwnerFields(IacTemplateResponse response, Accoun
9596
return;
9697
}
9798

99+
Domain domain = ApiDBUtils.findDomainById(owner.getDomainId());
100+
if (domain != null) {
101+
response.setDomainId(domain.getUuid());
102+
response.setDomainName(domain.getName());
103+
response.setDomainPath(domain.getPath());
104+
}
105+
98106
if (owner.getType() == Account.Type.PROJECT) {
99107
Project project = ApiDBUtils.findProjectByProjectAccountIdIncludingRemoved(owner.getId());
100-
response.setProjectId(project.getUuid());
101-
response.setProjectName(project.getName());
108+
if (project != null) {
109+
response.setProjectId(project.getUuid());
110+
response.setProjectName(project.getName());
111+
}
102112
} else {
103113
response.setAccountName(owner.getAccountName());
104114
response.setAccountId(owner.getUuid());
105115
}
106116
}
107117

108118
private void populateIacTemplateSharedEntitiesFields(IacTemplateResponse response, IacTemplate iacTemplate, Account caller, Account owner) {
109-
boolean isCallerAdmin = accountService.isAdmin(caller.getId());
110-
boolean isCallerTheIacTemplateOwner = caller.getId() == owner.getId();
111-
if (!isCallerAdmin && !isCallerTheIacTemplateOwner) {
112-
return;
119+
if (verifyCallerAccessToIacTemplateOwner(caller, owner)) {
120+
response.setSharedDomains(getSharedDomainResponses(iacTemplate.getDomainMappings()));
121+
Pair<List<IacTemplateResponse.SharedAccountResponse>, List<IacTemplateResponse.SharedProjectResponse>> sharedAccountAndProjectResponses = getSharedAccountAndProjectResponses(iacTemplate.getAccountMappings());
122+
response.setSharedAccounts(sharedAccountAndProjectResponses.first());
123+
response.setSharedProjects(sharedAccountAndProjectResponses.second());
113124
}
114-
115-
if (isCallerAdmin && !accountService.isRootAdmin(caller.getId())) {
116-
try {
117-
accountService.checkAccess(caller, null, false, owner);
118-
} catch (PermissionDeniedException e) {
119-
return;
120-
}
121-
}
122-
123-
// the above validation workflow could maybe be tranfered to the access check method
124-
response.setSharedDomains(getSharedDomainResponses(iacTemplate.getDomainMappings()));
125-
Pair<List<IacTemplateResponse.SharedAccountResponse>, List<IacTemplateResponse.SharedProjectResponse>> sharedAccountAndProjectResponses = getSharedAccountAndProjectResponses(iacTemplate.getAccountMappings());
126-
response.setSharedAccounts(sharedAccountAndProjectResponses.first());
127-
response.setSharedProjects(sharedAccountAndProjectResponses.second());
128125
}
129126

130127
private List<IacTemplateResponse.SharedDomainResponse> getSharedDomainResponses(List<IacTemplateDomainMapVO> domainMappings) {

plugins/iac/nimble/src/main/java/org/apache/cloudstack/persistence/iactemplates/IacTemplateAccountMapVO.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ public class IacTemplateAccountMapVO {
2929
@Column(name = "account_id", nullable = false)
3030
private long accountId;
3131

32+
public IacTemplateAccountMapVO() {
33+
}
34+
3235
public IacTemplateAccountMapVO(long iacTemplateId, long accountId) {
3336
this.iacTemplateId = iacTemplateId;
3437
this.accountId = accountId;

plugins/iac/nimble/src/main/java/org/apache/cloudstack/persistence/iactemplates/IacTemplateDomainMapVO.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ public class IacTemplateDomainMapVO {
2929
@Column(name = "domain_id", nullable = false)
3030
private long domainId;
3131

32+
public IacTemplateDomainMapVO() {
33+
}
34+
3235
public IacTemplateDomainMapVO(long iacTemplateId, long domainId) {
3336
this.iacTemplateId = iacTemplateId;
3437
this.domainId = domainId;

plugins/iac/nimble/src/main/java/org/apache/cloudstack/persistence/iactemplates/IacTemplateVO.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import javax.persistence.Temporal;
2828
import javax.persistence.TemporalType;
2929
import javax.persistence.Transient;
30+
import java.util.ArrayList;
3031
import java.util.Date;
3132
import java.util.List;
3233
import java.util.UUID;
@@ -69,10 +70,13 @@ public class IacTemplateVO implements IacTemplate {
6970
private Date removed;
7071

7172
@Transient
72-
private List<IacTemplateAccountMapVO> accountMappings;
73+
private List<IacTemplateAccountMapVO> accountMappings = new ArrayList<>();
7374

7475
@Transient
75-
private List<IacTemplateDomainMapVO> domainMappings;
76+
private List<IacTemplateDomainMapVO> domainMappings = new ArrayList<>();
77+
78+
public IacTemplateVO() {
79+
}
7680

7781
public IacTemplateVO(String name, String description, String iacTemplateContent, boolean recursiveDomains, long domainId, long accountId) {
7882
this.name = name;

plugins/iac/nimble/src/main/java/org/apache/cloudstack/service/NimbleManagerImpl.java

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
import org.apache.cloudstack.persistence.iactemplatesprofile.IacResourceTypeDao;
5050
import org.apache.cloudstack.persistence.iactemplatesprofile.IacResourceTypeVO;
5151
import org.apache.cloudstack.tosca.orchestrator.ToscaOrchestrator;
52-
import org.apache.commons.collections4.CollectionUtils;
5352
import org.apache.commons.lang3.ObjectUtils;
5453

5554
import javax.inject.Inject;
@@ -117,46 +116,81 @@ private boolean doesUserHaveAccessToNodeTypeApis(Pair<String, String> nodeTypeAp
117116
@Override
118117
public IacTemplateResponse registerIacTemplate(RegisterIacTemplateCmd cmd) {
119118
Account owner = accountService.getActiveAccountById(cmd.getEntityOwnerId());
120-
validateAccessToIacTemplateSharingEntities(owner, cmd);
119+
boolean isTemplateOwnerAdmin = accountService.isAdmin(owner.getId());
120+
if (!isTemplateOwnerAdmin) {
121+
if (cmd.isRecursiveDomains()) {
122+
throw new InvalidParameterValueException(String.format("An IaC template owned by [%s] cannot be shared recursively across different domains.", owner.getAccountName()));
123+
}
124+
125+
if (cmd.isTemplateShared()) {
126+
throw new InvalidParameterValueException(String.format("Account [%s] does not have permission to share IaC template with other entities.", owner.getAccountName()));
127+
}
128+
}
129+
130+
// validateAccessToIacTemplateSharingEntities(owner, cmd);
121131
toscaOrchestrator.parseServiceTemplate(cmd.getIacTemplateContent());
122132

123133
IacTemplate iacTemplate = persistIacTemplate(cmd, owner);
124134
if (iacTemplate == null) {
125135
throw new CloudRuntimeException("Unable to register IaC template.");
126136
}
127-
return responseBuilder.createIacTemplateResponse(iacTemplate, true);
137+
return responseBuilder.createIacTemplateResponse(iacTemplate, false);
128138
}
129139

130140
private IacTemplate persistIacTemplate(RegisterIacTemplateCmd cmd, Account owner) {
131141
IacTemplateVO iacTemplate = new IacTemplateVO(cmd.getName(), cmd.getDescription(), cmd.getIacTemplateContent(),
132142
cmd.isRecursiveDomains(), owner.getDomainId(), owner.getAccountId());
133143
return Transaction.execute((TransactionCallback<IacTemplate>) (status) -> {
134144
IacTemplateVO persistedTemplate = iacTemplateDao.persist(iacTemplate);
135-
List<IacTemplateDomainMapVO> domainMappings = cmd.getSharedDomainIds().stream()
136-
.map(domainId -> iacTemplateDomainMapDao.persist(new IacTemplateDomainMapVO(persistedTemplate.getId(), domainId)))
137-
.collect(Collectors.toList());
138-
List<IacTemplateAccountMapVO> accountMappings = cmd.getSharedAccountIds().stream()
139-
.map(accountId -> iacTemplateAccountMapDao.persist(new IacTemplateAccountMapVO(persistedTemplate.getId(), accountId)))
140-
.collect(Collectors.toList());
145+
List<IacTemplateDomainMapVO> domainMappings = persistDomainMappings(cmd.getSharedDomainIds(), persistedTemplate.getId());
146+
List<IacTemplateAccountMapVO> accountMappings = persistAccountMappings(cmd.getSharedAccountIds(), cmd.getSharedProjectIds(), persistedTemplate.getId(), owner);
141147
persistedTemplate.setDomainMappings(domainMappings);
142148
persistedTemplate.setAccountMappings(accountMappings);
143149
return persistedTemplate;
144150
});
145151
}
146152

147-
protected void validateAccessToIacTemplateSharingEntities(Account owner, RegisterIacTemplateCmd cmd) {
148-
boolean isTemplateOwnerAdmin = accountService.isAdmin(owner.getId());
149-
if (!isTemplateOwnerAdmin) {
150-
if (cmd.isRecursiveDomains()) {
151-
throw new InvalidParameterValueException(String.format("An IaC template owned by [%s] cannot be shared recursively across different domains.", owner.getAccountName()));
153+
private List<IacTemplateAccountMapVO> persistAccountMappings(List<Long> sharedAccountIds, List<Long> sharedProjectIds, long iacTemplateId, Account iacTemplateOwner) {
154+
List<IacTemplateAccountMapVO> accountMappings = new ArrayList<>();
155+
156+
for (Long accountId : sharedAccountIds) {
157+
Account account = accountService.getActiveAccountById(accountId);
158+
if (account == null) {
159+
throw new InvalidParameterValueException(String.format("Unable to find account with ID [%s].", accountId));
152160
}
161+
accountService.checkAccess(iacTemplateOwner, null, false, account);
162+
IacTemplateAccountMapVO accountMapping = new IacTemplateAccountMapVO(iacTemplateId, accountId);
163+
iacTemplateAccountMapDao.persist(accountMapping);
164+
accountMappings.add(accountMapping);
165+
}
153166

154-
if (CollectionUtils.isNotEmpty(cmd.getSharedDomainIds()) || CollectionUtils.isNotEmpty(cmd.getSharedAccountIds())
155-
|| CollectionUtils.isNotEmpty(cmd.getSharedProjectIds())) {
156-
throw new InvalidParameterValueException(String.format("Account [%s] does not have permission to share IaC template with other entities.", owner.getAccountName()));
167+
for (Long projectId : sharedProjectIds) {
168+
Project project = projectManager.getProject(projectId);
169+
if (project == null) {
170+
throw new InvalidParameterValueException(String.format("Unable to find project with ID [%s].", projectId));
157171
}
172+
if (!projectManager.canAccessProjectAccount(iacTemplateOwner, project.getProjectAccountId())) {
173+
throw new InvalidParameterValueException(String.format("Account [%s] does not have permission to share IaC template with project [%s].", iacTemplateOwner.getAccountName(), project.getName()));
174+
}
175+
IacTemplateAccountMapVO accountMapping = new IacTemplateAccountMapVO(iacTemplateId, project.getProjectAccountId());
176+
iacTemplateAccountMapDao.persist(accountMapping);
177+
accountMappings.add(accountMapping);
158178
}
159179

180+
return accountMappings;
181+
}
182+
183+
private List<IacTemplateDomainMapVO> persistDomainMappings(List<Long> sharedDomainIds, long iacTemplateId) {
184+
return sharedDomainIds.stream()
185+
.map(domainId -> {
186+
IacTemplateDomainMapVO domainMapping = new IacTemplateDomainMapVO(iacTemplateId, domainId);
187+
iacTemplateDomainMapDao.persist(domainMapping);
188+
return domainMapping;
189+
}).collect(Collectors.toList());
190+
}
191+
192+
protected void validateAccessToIacTemplateSharingEntities(Account owner, RegisterIacTemplateCmd cmd) {
193+
160194
cmd.getSharedDomainIds().forEach(domainId -> {
161195
Domain domain = domainManager.getDomain(domainId);
162196
if (domain == null) {
@@ -210,7 +244,7 @@ public List<Class<?>> getCommands() {
210244
if (!NimbleServiceEnabled.value()) {
211245
return commands;
212246
}
213-
return List.of(ListIacResourceTypesCmd.class, DeployIacTemplateCmd.class);
247+
return List.of(ListIacResourceTypesCmd.class, RegisterIacTemplateCmd.class, DeployIacTemplateCmd.class);
214248
}
215249

216250
@Override

0 commit comments

Comments
 (0)