Skip to content

Commit

Permalink
feat(agama): update gama deployment endpoint to support configuration…
Browse files Browse the repository at this point in the history
… properties (#4049)

* docs: update API description #4032

* feat: add configs endpoint #4032

* chore: sync swagger descriptor #4032

* chore: reduce code smells #4032

* chore: reduce code smells #4032
  • Loading branch information
jgomer2001 authored Mar 7, 2023
1 parent e418e57 commit 392525c
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 9 deletions.
19 changes: 19 additions & 0 deletions docs/admin/developer/agama/gama-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ Once a `.gama` file is built the deployment process follows. Here is a typical w

1. Send (POST) the archive contents to the deployment endpoint. Normally a 202 response should be obtained meaning a task has been queued for processing
1. Poll (via GET) the status of the deployment. When the archive has been effectively deployed a status of 200 should be obtained. It may take up to 30 seconds for the process to complete once the archive is sent. This time may extend if there is another deployment in course
1. Optionally supply configuration parameters for flows if needed. This is done via PUT to the `/configs` endpoint. The response of the previous step may contain descriptive/sample configurations that can be used as a guide
1. Test the deployed flows and adjust the archive for any changes required
1. Go to point 1 if needed
1. If desired, a request can be sent to undeploy the flows (via DELETE)
Expand Down Expand Up @@ -161,6 +162,24 @@ The following tables summarize the available endpoints. All URLs are relative to
|Status|202 (the task was created and scheduled for deployment), 409 (there is a task already for this project and it hasn't finished yet), 400 (a param is missing)|


|Endpoint -> |`/agama-deployment/configs`|
|-|-|
|Purpose|Retrieve the configurations associated to flows that belong to the project of interest. The project must have been already processed fully|
|Method|GET|
|Query params|`name` (the project's name) - mandatory|
|Output|A JSON object whose properties are flow names and values correspond to configuration properties defined (JSON objects too)|
|Status|200 (successful response), 409 (the project is being deployed currently), 404 (unknown project), 400 (a param is missing)|


|Endpoint -> |`/agama-deployment/configs`|
|-|-|
|Purpose|Set or replace the configurations associated to flows that belong to the project of interest. The project must have been already processed fully|
|Method|PUT|
|Query params|`name` (the project's name) - mandatory|
|Output|A JSON object whose properties are flow names and values correspond to a boolean indicating the success of the update for the given flow|
|Status|200 (successful response), 409 (the project is being deployed currently), 404 (unknown project), 400 (a param is missing)|


|Endpoint -> |`/agama-deployment`|
|-|-|
|Purpose|Undeploy an ADS project from the server. Entails removing flows and assets initally supplied|
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.jans.ads.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Map;

@JsonIgnoreProperties(ignoreUnknown = true)
public class ProjectMetadata {
Expand All @@ -10,6 +13,9 @@ public class ProjectMetadata {
private String type;
private String description;

@JsonProperty("configs")
private Map<String, Object> configHints;

public String getProjectName() {
return projectName;
}
Expand Down Expand Up @@ -42,4 +48,12 @@ public void setDescription(String description) {
this.description = description;
}

public Map<String, Object> getConfigHints() {
return configHints;
}

public void setConfigHints(Map<String, Object> configHints) {
this.configHints = configHints;
}

}
60 changes: 51 additions & 9 deletions jans-config-api/docs/jans-config-api-swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,40 @@ paths:
security:
- oauth2:
- https://jans.io/oauth/config/agama.delete
/api/v1/agama-deployment/configs:
get:
operationId: getConfigs
parameters:
- name: name
in: query
schema:
type: string
responses:
default:
description: default response
content:
application/json: {}
put:
operationId: setConfigs
parameters:
- name: name
in: query
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
additionalProperties:
type: object
additionalProperties:
type: object
responses:
default:
description: default response
content:
application/json: {}
/api/v1/agama-deployment/list:
get:
tags:
Expand Down Expand Up @@ -7355,6 +7389,10 @@ components:
type: string
description:
type: string
configs:
type: object
additionalProperties:
type: object
PagedResult:
type: object
properties:
Expand Down Expand Up @@ -7535,20 +7573,20 @@ components:
$ref: '#/components/schemas/AttributeValidation'
tooltip:
type: string
adminCanEdit:
type: boolean
userCanView:
adminCanAccess:
type: boolean
adminCanView:
type: boolean
userCanEdit:
adminCanEdit:
type: boolean
userCanAccess:
type: boolean
adminCanAccess:
userCanView:
type: boolean
whitePagesCanView:
type: boolean
userCanEdit:
type: boolean
baseDn:
type: string
PatchRequest:
Expand Down Expand Up @@ -7899,6 +7937,8 @@ components:
type: boolean
returnClientSecretOnRead:
type: boolean
rotateClientRegistrationAccessTokenOnUsage:
type: boolean
rejectJwtWithNoneAlg:
type: boolean
expirationNotificatorEnabled:
Expand Down Expand Up @@ -7953,6 +7993,8 @@ components:
type: boolean
disablePromptLogin:
type: boolean
disablePromptConsent:
type: boolean
sessionIdLifetime:
type: integer
format: int32
Expand Down Expand Up @@ -8268,6 +8310,8 @@ components:
type: object
additionalProperties:
type: string
fapi:
type: boolean
allResponseTypesSupported:
uniqueItems: true
type: array
Expand All @@ -8277,8 +8321,6 @@ components:
- code
- token
- id_token
fapi:
type: boolean
AuthenticationFilter:
required:
- baseDn
Expand Down Expand Up @@ -8915,10 +8957,10 @@ components:
type: array
items:
type: object
displayValue:
type: string
value:
type: object
displayValue:
type: string
LocalizedString:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package io.jans.configapi.rest.resource.auth;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.jans.ads.model.Deployment;
import io.jans.agama.model.Flow;
import io.jans.as.model.util.Pair;
import io.jans.orm.model.PagedResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
Expand All @@ -13,19 +18,31 @@
import io.jans.configapi.util.ApiAccessConstants;
import io.jans.configapi.util.ApiConstants;
import io.jans.configapi.service.auth.AgamaDeploymentsService;
import io.jans.configapi.service.auth.AgamaFlowService;

import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.HashMap;

@Path(ApiConstants.AGAMA_DEPLOYMENTS)
@Produces(MediaType.APPLICATION_JSON)
public class AgamaDeploymentsResource extends ConfigBaseResource {

@Inject
private AgamaDeploymentsService ads;

@Inject
private AgamaFlowService flowService;

private ObjectMapper mapper;

@Operation(summary = "Retrieve the list of projects deployed currently.", description = "Retrieve the list of projects deployed currently.", operationId = "get-agama-dev-prj", tags = {
"Agama - Developer Studio" }, security = @SecurityRequirement(name = "oauth2", scopes = {
ApiAccessConstants.AGAMA_READ_ACCESS, ApiAccessConstants.AGAMA_WRITE_ACCESS,
Expand Down Expand Up @@ -148,4 +165,96 @@ public Response undeploy(@QueryParam("name") String projectName) {

}

@GET
@Path("configs")
@ProtectedApi(scopes = { ApiAccessConstants.AGAMA_READ_ACCESS }, groupScopes = {
ApiAccessConstants.AGAMA_WRITE_ACCESS }, superScopes = { ApiAccessConstants.SUPER_ADMIN_READ_ACCESS })
public Response getConfigs(@QueryParam("name") String projectName) throws JsonProcessingException {

Pair<Response, Set<String>> pair = projectFlows(projectName);
Response resp = pair.getFirst();
if (resp != null) return resp;

Map<String, Map<String, Object>> configs = new HashMap<>();

for (String qname : pair.getSecond()) {
Map<String, Object> config = Optional.ofNullable(flowService.getFlowByName(qname))
.map(f -> f.getMetadata().getProperties()).orElse(null);

if (config == null) {
logger.warn("Flow {} does not exist or has no configuration properties defined", qname);
} else {
logger.debug("Adding flow properties of {}", qname);
configs.put(qname, config);
}
}
//Use own mapper so any empty maps that may be found inside flows configurations are not ignored
return Response.ok(mapper.writeValueAsString(configs)).build();

}

@PUT
@Path("configs")
@Consumes(MediaType.APPLICATION_JSON)
@ProtectedApi(scopes = { ApiAccessConstants.AGAMA_WRITE_ACCESS },
superScopes = { ApiAccessConstants.SUPER_ADMIN_WRITE_ACCESS })
public Response setConfigs(@QueryParam("name") String projectName,
Map<String, Map<String, Object>> flowsConfigs) {

if (flowsConfigs == null) {
return Response.status(Response.Status.BAD_REQUEST)
.entity("Mapping of flows vs. configs not provided").build();
}

Pair<Response, Set<String>> pair = projectFlows(projectName);
Response resp = pair.getFirst();
if (resp != null) return resp;

Set<String> flowIds = pair.getSecond();
Map<String, Boolean> results = new HashMap<>();

for (String qname : flowsConfigs.keySet()) {
if (qname != null && flowIds.contains(qname)) {

Flow flow = flowService.getFlowByName(qname);
boolean success = false;

if (flow == null) {
logger.warn("Unable to retrieve flow {}", qname);
} else {
try {
flow.getMetadata().setProperties(flowsConfigs.get(qname));
flowService.updateFlow(flow);
success = true;
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
results.put(qname, success);

} else if (logger.isWarnEnabled()) {
logger.warn("Flow {} is not part of project {}, config ignored", qname,
projectName.replaceAll("[\n\r]", "_"));
}
}
return Response.ok(results).build();

}

private Pair<Response, Set<String>> projectFlows(String projectName) {

Response res = getDeployment(projectName);
if (res.getStatus() != Response.Status.OK.getStatusCode()) return new Pair<>(res, null);

Deployment d = (Deployment) res.getEntity();
//Retrieve the flows this project contains
return new Pair<>(null, d.getDetails().getFlowsError().keySet());

}

@PostConstruct
private void init() {
mapper = new ObjectMapper();
}

}

0 comments on commit 392525c

Please sign in to comment.