Skip to content

Commit 580c99d

Browse files
authored
Merge pull request #2212 from harveymmaunders/feat/decorator-storage
feat(calm-hub): decorator resource and endpoint to get decorators
2 parents 3a3e656 + 23be1fe commit 580c99d

File tree

13 files changed

+1566
-1
lines changed

13 files changed

+1566
-1
lines changed

calm-hub/mongo/init-mongo.js

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ if (db.counters.countDocuments({ _id: "userAccessStoreCounter" }) === 0) {
6262
print("userAccessStoreCounter already exists, no initialization needed");
6363
}
6464

65+
if (db.counters.countDocuments({ _id: "decoratorStoreCounter" }) === 0) {
66+
db.counters.insertOne({
67+
_id: "decoratorStoreCounter",
68+
sequence_value: 1
69+
});
70+
print("Initialized decoratorStoreCounter with sequence_value 1");
71+
} else {
72+
print("decoratorStoreCounter already exists, no initialization needed");
73+
}
74+
6575
db.schemas.insertMany([ // Insert initial documents into the schemas collection
6676
{
6777
version: "2025-03",
@@ -2814,4 +2824,135 @@ Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliqu
28142824
},
28152825
],
28162826
},
2817-
]);
2827+
]);
2828+
2829+
db.decorators.insertMany([
2830+
{
2831+
namespace: "finos",
2832+
decorators: [
2833+
{
2834+
decoratorId: NumberInt(1),
2835+
decorator: {
2836+
"$schema": "https://calm.finos.org/draft/2026-03/standards/deployment/deployment.decorator.schema.json",
2837+
"unique-id": "finos-architecture-1-deployment",
2838+
"type": "deployment",
2839+
"target": [
2840+
"/calm/namespaces/finos/architectures/1/versions/1-0-0"
2841+
],
2842+
"target-type": [
2843+
"architecture"
2844+
],
2845+
"applies-to": [
2846+
"example-node"
2847+
],
2848+
"data": {
2849+
"start-time": "2026-02-23T10:00:00Z",
2850+
"end-time": "2026-02-23T10:05:30Z",
2851+
"status": "completed",
2852+
"observability": "https://grafana.example.com/d/finos-architecture-1",
2853+
"deployment-url": "https://jenkins.example.com/job/finos-architecture/123/",
2854+
"notes": "Production deployment of FINOS Architecture 1 with baseline configuration"
2855+
}
2856+
}
2857+
},
2858+
{
2859+
decoratorId: NumberInt(2),
2860+
decorator: {
2861+
"$schema": "https://calm.finos.org/draft/2026-03/standards/deployment/deployment.decorator.schema.json",
2862+
"unique-id": "finos-architecture-1-deployment-v2",
2863+
"type": "deployment",
2864+
"target": [
2865+
"/calm/namespaces/finos/architectures/1/versions/1-0-0"
2866+
],
2867+
"target-type": [
2868+
"architecture"
2869+
],
2870+
"applies-to": [
2871+
"example-node"
2872+
],
2873+
"data": {
2874+
"start-time": "2026-03-04T15:00:00Z",
2875+
"end-time": "2026-03-04T15:08:15Z",
2876+
"status": "completed",
2877+
"notes": "Second production deployment of FINOS Architecture 1 with performance improvements and bug fixes"
2878+
}
2879+
}
2880+
},
2881+
{
2882+
decoratorId: NumberInt(3),
2883+
decorator: {
2884+
"$schema": "https://calm.finos.org/draft/2026-03/standards/deployment/deployment.decorator.schema.json",
2885+
"unique-id": "finos-pattern-1-deployment",
2886+
"type": "deployment",
2887+
"target": [
2888+
"/calm/namespaces/finos/patterns/1/versions/1-0-0"
2889+
],
2890+
"target-type": [
2891+
"pattern"
2892+
],
2893+
"applies-to": [
2894+
"node-a", "relationship-x"
2895+
],
2896+
"data": {
2897+
"start-time": "2026-02-15T09:30:00Z",
2898+
"end-time": "2026-02-15T09:35:20Z",
2899+
"status": "completed",
2900+
"deployment-url": "https://github.com/finos/actions/runs/987654321",
2901+
"notes": "Pattern deployment via GitHub Actions"
2902+
}
2903+
}
2904+
}
2905+
]
2906+
},
2907+
{
2908+
namespace: "workshop",
2909+
decorators: [
2910+
{
2911+
decoratorId: NumberInt(1),
2912+
decorator: {
2913+
"$schema": "https://calm.finos.org/draft/2026-03/standards/deployment/deployment.decorator.schema.json",
2914+
"unique-id": "workshop-conference-deployment",
2915+
"type": "deployment",
2916+
"target": [
2917+
"/calm/namespaces/workshop/architectures/1/versions/1-0-0"
2918+
],
2919+
"target-type": [
2920+
"architecture"
2921+
],
2922+
"applies-to": [
2923+
"conference-website",
2924+
"load-balancer"
2925+
],
2926+
"data": {
2927+
"start-time": "2026-03-01T14:30:00Z",
2928+
"end-time": "2026-03-01T14:35:45Z",
2929+
"status": "completed",
2930+
"deployment-url": "https://vercel.com/workshop/deployments/abc123xyz",
2931+
"notes": "Workshop conference system deployment via Vercel"
2932+
}
2933+
}
2934+
},
2935+
{
2936+
decoratorId: NumberInt(2),
2937+
decorator: {
2938+
"$schema": "https://calm.finos.org/draft/2026-03/standards/observability/observability.decorator.schema.json",
2939+
"unique-id": "workshop-conference-monitoring",
2940+
"type": "observability",
2941+
"target": [
2942+
"/calm/namespaces/workshop/architectures/1/versions/1-0-0"
2943+
],
2944+
"target-type": [
2945+
"architecture"
2946+
],
2947+
"applies-to": [
2948+
"conference-website"
2949+
],
2950+
"data": {
2951+
"dashboard-url": "https://datadog.example.com/dashboard/workshop-conference",
2952+
"notes": "Monitoring dashboard for workshop conference system"
2953+
}
2954+
}
2955+
}
2956+
]
2957+
}
2958+
]);

calm-hub/src/main/java/org/finos/calm/config/NitriteDBConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ private void initializeDatabase() throws IOException, JsonParseException {
9292
db.getCollection("flows");
9393
db.getCollection("schemas");
9494
db.getCollection("counters");
95+
db.getCollection("decorators");
9596
}
9697

9798
/**
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.finos.calm.resources;
2+
3+
import jakarta.inject.Inject;
4+
import jakarta.validation.constraints.Pattern;
5+
import jakarta.validation.constraints.Size;
6+
import jakarta.ws.rs.GET;
7+
import jakarta.ws.rs.Path;
8+
import jakarta.ws.rs.PathParam;
9+
import jakarta.ws.rs.Produces;
10+
import jakarta.ws.rs.QueryParam;
11+
import jakarta.ws.rs.core.MediaType;
12+
import jakarta.ws.rs.core.Response;
13+
import org.eclipse.microprofile.openapi.annotations.Operation;
14+
import org.finos.calm.domain.ValueWrapper;
15+
import org.finos.calm.domain.exception.NamespaceNotFoundException;
16+
import org.finos.calm.security.CalmHubScopes;
17+
import org.finos.calm.security.PermittedScopes;
18+
import org.finos.calm.store.DecoratorStore;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
import static org.finos.calm.resources.ResourceValidationConstants.NAMESPACE_MESSAGE;
23+
import static org.finos.calm.resources.ResourceValidationConstants.NAMESPACE_REGEX;
24+
import static org.finos.calm.resources.ResourceValidationConstants.QUERY_PARAM_NO_WHITESPACE_MESSAGE;
25+
import static org.finos.calm.resources.ResourceValidationConstants.QUERY_PARAM_NO_WHITESPACE_REGEX;
26+
27+
/**
28+
* Resource for managing decorators in a given namespace
29+
*/
30+
@Path("/calm/namespaces")
31+
public class DecoratorResource {
32+
33+
private final DecoratorStore decoratorStore;
34+
private final Logger logger = LoggerFactory.getLogger(DecoratorResource.class);
35+
36+
@Inject
37+
public DecoratorResource(DecoratorStore decoratorStore) {
38+
this.decoratorStore = decoratorStore;
39+
}
40+
41+
/**
42+
* Retrieve a list of decorator IDs in a given namespace with optional filtering
43+
*
44+
* @param namespace the namespace to retrieve decorators for
45+
* @param target optional target path to filter by (e.g., "/calm/namespaces/finos/architectures/1/versions/1-0-0")
46+
* @param type optional decorator type to filter by (e.g., "deployment", "observability")
47+
* @return a list of decorator IDs matching the criteria
48+
*/
49+
@GET
50+
@Path("{namespace}/decorators")
51+
@Produces(MediaType.APPLICATION_JSON)
52+
@Operation(
53+
summary = "Retrieve decorators in a given namespace",
54+
description = "Decorator IDs stored in a given namespace, optionally filtered by target and/or type"
55+
)
56+
@PermittedScopes({CalmHubScopes.ARCHITECTURES_ALL, CalmHubScopes.ARCHITECTURES_READ})
57+
public Response getDecoratorsForNamespace(
58+
@PathParam("namespace") @Pattern(regexp = NAMESPACE_REGEX, message = NAMESPACE_MESSAGE) String namespace,
59+
@QueryParam("target") @Size(max = 500) @Pattern(regexp = QUERY_PARAM_NO_WHITESPACE_REGEX, message = QUERY_PARAM_NO_WHITESPACE_MESSAGE) String target,
60+
@QueryParam("type") @Size(max = 100) @Pattern(regexp = QUERY_PARAM_NO_WHITESPACE_REGEX, message = QUERY_PARAM_NO_WHITESPACE_MESSAGE) String type
61+
) {
62+
try {
63+
return Response.ok(new ValueWrapper<>(decoratorStore.getDecoratorsForNamespace(namespace, target, type))).build();
64+
} catch (NamespaceNotFoundException e) {
65+
logger.error("Invalid namespace [{}] when retrieving decorators", namespace, e);
66+
return CalmResourceErrorResponses.invalidNamespaceResponse(namespace);
67+
}
68+
}
69+
}

calm-hub/src/main/java/org/finos/calm/resources/ResourceValidationConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public class ResourceValidationConstants {
1010
public static final String DOMAIN_NAME_MESSAGE = "domain name must match pattern '^[A-Za-z0-9-]+$'";
1111
public static final String VERSION_REGEX = "^(0|[1-9][0-9]*)[-.]?(0|[1-9][0-9]*)[-.]?(0|[1-9][0-9]*)$";
1212
public static final String VERSION_MESSAGE = "version must match pattern '^(0|[1-9][0-9]*)[-.]?(0|[1-9][0-9]*)[-.]?(0|[1-9][0-9]*)$'";
13+
public static final String QUERY_PARAM_NO_WHITESPACE_REGEX = "^[A-Za-z0-9_/-]+$";
14+
public static final String QUERY_PARAM_NO_WHITESPACE_MESSAGE = "Query parameter must contain only alphanumeric characters, hyphens, underscores, and forward slashes";
1315
public static final PolicyFactory STRICT_SANITIZATION_POLICY = new HtmlPolicyBuilder().toFactory();
1416

1517
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.finos.calm.store;
2+
3+
import org.finos.calm.domain.exception.NamespaceNotFoundException;
4+
5+
import java.util.List;
6+
7+
/**
8+
* Interface for decorator storage operations.
9+
*/
10+
public interface DecoratorStore {
11+
12+
/**
13+
* Retrieve decorator IDs for a given namespace with optional filtering.
14+
*
15+
* @param namespace the namespace to retrieve decorators for
16+
* @param target optional target path to filter by (e.g., "/calm/namespaces/finos/architectures/1/versions/1-0-0")
17+
* @param type optional decorator type to filter by (e.g., "deployment", "observability")
18+
* @return a list of decorator IDs matching the criteria
19+
* @throws NamespaceNotFoundException if the namespace does not exist
20+
*/
21+
List<Integer> getDecoratorsForNamespace(String namespace, String target, String type) throws NamespaceNotFoundException;
22+
}

0 commit comments

Comments
 (0)