-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add endpoints for MVP ADS projects management #3094
- Loading branch information
1 parent
cc6c5e1
commit 2b0c97a
Showing
4 changed files
with
247 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
...api/server/src/main/java/io/jans/configapi/rest/resource/auth/ADSDeploymentsResource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package io.jans.configapi.rest.resource.auth; | ||
|
||
import io.jans.ads.model.Deployment; | ||
import io.jans.orm.model.PagedResult; | ||
import io.jans.configapi.core.rest.ProtectedApi; | ||
import io.jans.configapi.util.ApiAccessConstants; | ||
import io.jans.configapi.util.ApiConstants; | ||
import io.jans.configapi.service.auth.ADSDeploymentsService; | ||
|
||
import jakarta.inject.Inject; | ||
import jakarta.ws.rs.*; | ||
import jakarta.ws.rs.core.MediaType; | ||
import jakarta.ws.rs.core.Response; | ||
|
||
@Path(ApiConstants.ADS_DEPLOYMENTS) | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public class ADSDeploymentsResource extends ConfigBaseResource { | ||
|
||
@Inject | ||
private ADSDeploymentsService ads; | ||
|
||
@GET | ||
@Path("list") | ||
@ProtectedApi(scopes = { ApiAccessConstants.AGAMA_READ_ACCESS }, groupScopes = { | ||
ApiAccessConstants.AGAMA_WRITE_ACCESS }, superScopes = { ApiAccessConstants.SUPER_ADMIN_READ_ACCESS }) | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public Response getDeployments(@QueryParam("start") int start, @QueryParam("count") int count) { | ||
|
||
// this is NOT a search but a paged listing | ||
int maxcount = getMaxCount(); | ||
PagedResult<Deployment> res = ads.list(start > 0 ? start - 1 : 0, count > 0 ? count : maxcount, maxcount); | ||
res.getEntries().forEach(d -> d.getDetails().setFolders(null)); | ||
res.setStart(start + 1); | ||
return Response.ok(res).build(); | ||
|
||
} | ||
|
||
@GET | ||
@ProtectedApi(scopes = { ApiAccessConstants.AGAMA_READ_ACCESS }, groupScopes = { | ||
ApiAccessConstants.AGAMA_WRITE_ACCESS }, superScopes = { ApiAccessConstants.SUPER_ADMIN_READ_ACCESS }) | ||
@Produces(MediaType.APPLICATION_JSON) | ||
public Response getDeployment(@QueryParam("name") String projectName) { | ||
|
||
if (projectName == null) { | ||
return Response.status(Response.Status.BAD_REQUEST) | ||
.entity("Project name missing in query string").build(); | ||
} | ||
|
||
Deployment d = ads.getDeployment(projectName); | ||
|
||
if (d == null) | ||
return Response.status(Response.Status.NOT_FOUND) | ||
.entity("Unknown project " + projectName).build(); | ||
|
||
if (d.getFinishedAt() == null) | ||
return Response.noContent().build(); | ||
|
||
d.getDetails().setFolders(null); | ||
return Response.ok(d).build(); | ||
|
||
} | ||
|
||
@POST | ||
@Consumes("application/zip") | ||
@ProtectedApi(scopes = { ApiAccessConstants.AGAMA_WRITE_ACCESS }, | ||
superScopes = { ApiAccessConstants.SUPER_ADMIN_WRITE_ACCESS }) | ||
public Response deploy(@QueryParam("name") String projectName, byte[] gamaBinary) { | ||
|
||
if (projectName == null || gamaBinary == null) | ||
return Response.status(Response.Status.BAD_REQUEST) | ||
.entity("Project name or binary data missing").build(); | ||
|
||
if (ads.createDeploymentTask(projectName, gamaBinary)) | ||
return Response.accepted().entity("A deployment task for project " + projectName + | ||
" has been queued. Use the GET endpoint to poll status").build(); | ||
|
||
return Response.status(Response.Status.CONFLICT) | ||
.entity("There is an active deployment task for " + projectName + | ||
". Wait for an OK response from the GET endpoint").build(); | ||
|
||
} | ||
|
||
@DELETE | ||
@ProtectedApi(scopes = { ApiAccessConstants.AGAMA_WRITE_ACCESS }, | ||
superScopes = { ApiAccessConstants.SUPER_ADMIN_WRITE_ACCESS }) | ||
public Response undeploy(@QueryParam("name") String projectName) { | ||
|
||
if (projectName == null) | ||
return Response.status(Response.Status.BAD_REQUEST) | ||
.entity("Project name missing in query string").build(); | ||
|
||
Boolean result = ads.createUndeploymentTask(projectName); | ||
|
||
if (result == null) | ||
return Response.status(Response.Status.NOT_FOUND) | ||
.entity("Unknown project " + projectName).build(); | ||
|
||
if (!result) | ||
return Response.status(Response.Status.CONFLICT) | ||
.entity("Cannot undeploy project " + projectName + ": it is currently being deployed").build(); | ||
|
||
return Response.noContent().build(); | ||
|
||
} | ||
|
||
} |
139 changes: 139 additions & 0 deletions
139
...config-api/server/src/main/java/io/jans/configapi/service/auth/ADSDeploymentsService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package io.jans.configapi.service.auth; | ||
|
||
import io.jans.ads.model.Deployment; | ||
import io.jans.ads.model.DeploymentDetails; | ||
import io.jans.orm.model.PagedResult; | ||
import io.jans.orm.PersistenceEntryManager; | ||
import io.jans.orm.search.filter.Filter; | ||
|
||
import jakarta.enterprise.context.ApplicationScoped; | ||
import jakarta.inject.Inject; | ||
|
||
import java.util.Base64; | ||
import java.util.Date; | ||
|
||
import org.slf4j.Logger; | ||
|
||
import static java.nio.charset.StandardCharsets.UTF_8; | ||
|
||
@ApplicationScoped | ||
public class ADSDeploymentsService { | ||
|
||
private static final String BASE_DN = "ou=deployments,ou=agama,o=jans"; | ||
|
||
@Inject | ||
private Logger logger; | ||
|
||
@Inject | ||
private PersistenceEntryManager entryManager; | ||
|
||
public PagedResult<Deployment> list(int start, int count, int maxCount) { | ||
|
||
String[] attrs = new String[]{ "jansId", "jansStartDate", "jansActive", | ||
"jansEndDate", "adsPrjDeplDetails" }; | ||
Filter filter = Filter.createPresenceFilter("jansId"); | ||
|
||
return entryManager.findPagedEntries(BASE_DN, Deployment.class, | ||
filter, attrs, "jansStartDate", null, start, count, maxCount); | ||
|
||
} | ||
|
||
public Deployment getDeployment(String name) { | ||
|
||
String[] attrs = new String[]{ "jansStartDate", "jansEndDate", "adsPrjDeplDetails" }; | ||
logger.info("Looking up project named {}", name); | ||
|
||
Deployment d = null; | ||
try { | ||
d = entryManager.find(dnOfProject(idFromName(name)), Deployment.class, attrs); | ||
} catch (Exception e) { | ||
logger.warn(e.getMessage()); | ||
} | ||
return d; | ||
|
||
} | ||
|
||
public boolean createDeploymentTask(String name, byte[] gamaBinary) { | ||
|
||
Deployment d = null; | ||
String id = idFromName(name); | ||
try { | ||
String[] attrs = new String[]{ "jansActive", "jansEndDate", "dn" }; | ||
d = entryManager.find(dnOfProject(id), Deployment.class, attrs); | ||
} catch (Exception e) { | ||
logger.debug("No already existing deployment for project {}", name); | ||
} | ||
|
||
boolean existing = d != null; | ||
if (existing && d.getFinishedAt() == null) { | ||
logger.info("A deployment is still in course for this project!"); | ||
|
||
if (!d.isTaskActive()) { | ||
logger.info("No node is in charge of this task yet"); | ||
} | ||
return false; | ||
} | ||
|
||
DeploymentDetails dd = new DeploymentDetails(); | ||
dd.setProjectName(name); | ||
|
||
if (!existing) { | ||
d = new Deployment(); | ||
d.setDn(dnOfProject(id)); | ||
} | ||
d.setId(id); | ||
d.setCreatedAt(new Date()); | ||
d.setTaskActive(false); | ||
d.setFinishedAt(null); | ||
d.setDetails(dd); | ||
|
||
byte[] encoded = Base64.getEncoder().encode(gamaBinary); | ||
d.setAssets(new String(encoded, UTF_8)); | ||
|
||
logger.info("Persisting deployment task for project {}", name); | ||
if (existing) { | ||
entryManager.merge(d); | ||
} else { | ||
entryManager.persist(d); | ||
} | ||
return true; | ||
|
||
} | ||
|
||
public Boolean createUndeploymentTask(String name) { | ||
|
||
String dn = dnOfProject(idFromName(name)); | ||
String[] attrs = new String[]{ "jansActive", "dn" }; | ||
Deployment d = null; | ||
|
||
try { | ||
d = entryManager.find(dn, Deployment.class, attrs); | ||
} catch (Exception e) { | ||
logger.warn(e.getMessage()); | ||
} | ||
|
||
if (d == null) return null; | ||
|
||
//A project can be undeployed when there is no deployment in course or | ||
//when there is but no node has taken it yet | ||
boolean deploymentInProcess = d.isTaskActive(); | ||
if (!deploymentInProcess) { | ||
logger.info("Removing deployment of project {}", name); | ||
entryManager.remove(dn, Deployment.class); | ||
} | ||
|
||
return !deploymentInProcess; | ||
|
||
} | ||
|
||
private static String dnOfProject(String prjId) { | ||
return String.format("jansId=%s,%s", prjId, BASE_DN); | ||
} | ||
|
||
private static String idFromName(String name) { | ||
String hash = Integer.toString(name.hashCode()); | ||
if (hash.startsWith("-")) hash = hash.substring(1); | ||
return hash; | ||
} | ||
|
||
} |