Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 142 additions & 1 deletion calm-hub/mongo/init-mongo.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ if (db.counters.countDocuments({ _id: "userAccessStoreCounter" }) === 0) {
print("userAccessStoreCounter already exists, no initialization needed");
}

if (db.counters.countDocuments({ _id: "decoratorStoreCounter" }) === 0) {
db.counters.insertOne({
_id: "decoratorStoreCounter",
sequence_value: 1
});
print("Initialized decoratorStoreCounter with sequence_value 1");
} else {
print("decoratorStoreCounter already exists, no initialization needed");
}

db.schemas.insertMany([ // Insert initial documents into the schemas collection
{
version: "2025-03",
Expand Down Expand Up @@ -2814,4 +2824,135 @@ Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliqu
},
],
},
]);
]);

db.decorators.insertMany([
{
namespace: "finos",
decorators: [
{
decoratorId: NumberInt(1),
decorator: {
"$schema": "https://calm.finos.org/draft/2026-03/standards/deployment/deployment.decorator.schema.json",
"unique-id": "finos-architecture-1-deployment",
"type": "deployment",
"target": [
"/calm/namespaces/finos/architectures/1/versions/1-0-0"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not entirely sure how I feel about this target reference. We can include architecture id, but how do differ between pattersn / architectures and also the version>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, this is currently how MongoDB and CALM Hub have been setup. This follows the rest of how CALM Hub works and when this is changed, it will be updated along with the rest #1853.

],
"target-type": [
"architecture"
],
"applies-to": [
"example-node"
],
"data": {
"start-time": "2026-02-23T10:00:00Z",
"end-time": "2026-02-23T10:05:30Z",
"status": "completed",
"observability": "https://grafana.example.com/d/finos-architecture-1",
"deployment-url": "https://jenkins.example.com/job/finos-architecture/123/",
"notes": "Production deployment of FINOS Architecture 1 with baseline configuration"
}
}
},
{
decoratorId: NumberInt(2),
decorator: {
"$schema": "https://calm.finos.org/draft/2026-03/standards/deployment/deployment.decorator.schema.json",
"unique-id": "finos-architecture-1-deployment-v2",
"type": "deployment",
"target": [
"/calm/namespaces/finos/architectures/1/versions/1-0-0"
],
"target-type": [
"architecture"
],
"applies-to": [
"example-node"
],
"data": {
"start-time": "2026-03-04T15:00:00Z",
"end-time": "2026-03-04T15:08:15Z",
"status": "completed",
"notes": "Second production deployment of FINOS Architecture 1 with performance improvements and bug fixes"
}
}
},
{
decoratorId: NumberInt(3),
decorator: {
"$schema": "https://calm.finos.org/draft/2026-03/standards/deployment/deployment.decorator.schema.json",
"unique-id": "finos-pattern-1-deployment",
"type": "deployment",
"target": [
"/calm/namespaces/finos/patterns/1/versions/1-0-0"
],
"target-type": [
"pattern"
],
"applies-to": [
"node-a", "relationship-x"
],
"data": {
"start-time": "2026-02-15T09:30:00Z",
"end-time": "2026-02-15T09:35:20Z",
"status": "completed",
"deployment-url": "https://github.com/finos/actions/runs/987654321",
"notes": "Pattern deployment via GitHub Actions"
}
}
}
]
},
{
namespace: "workshop",
decorators: [
{
decoratorId: NumberInt(1),
decorator: {
"$schema": "https://calm.finos.org/draft/2026-03/standards/deployment/deployment.decorator.schema.json",
"unique-id": "workshop-conference-deployment",
"type": "deployment",
"target": [
"/calm/namespaces/workshop/architectures/1/versions/1-0-0"
],
"target-type": [
"architecture"
],
"applies-to": [
"conference-website",
"load-balancer"
],
"data": {
"start-time": "2026-03-01T14:30:00Z",
"end-time": "2026-03-01T14:35:45Z",
"status": "completed",
"deployment-url": "https://vercel.com/workshop/deployments/abc123xyz",
"notes": "Workshop conference system deployment via Vercel"
}
}
},
{
decoratorId: NumberInt(2),
decorator: {
"$schema": "https://calm.finos.org/draft/2026-03/standards/observability/observability.decorator.schema.json",
"unique-id": "workshop-conference-monitoring",
"type": "observability",
"target": [
"/calm/namespaces/workshop/architectures/1/versions/1-0-0"
],
"target-type": [
"architecture"
],
"applies-to": [
"conference-website"
],
"data": {
"dashboard-url": "https://datadog.example.com/dashboard/workshop-conference",
"notes": "Monitoring dashboard for workshop conference system"
}
}
}
]
}
]);
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ private void initializeDatabase() throws IOException, JsonParseException {
db.getCollection("flows");
db.getCollection("schemas");
db.getCollection("counters");
db.getCollection("decorators");
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.finos.calm.resources;

import jakarta.inject.Inject;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.finos.calm.domain.ValueWrapper;
import org.finos.calm.domain.exception.NamespaceNotFoundException;
import org.finos.calm.security.CalmHubScopes;
import org.finos.calm.security.PermittedScopes;
import org.finos.calm.store.DecoratorStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.finos.calm.resources.ResourceValidationConstants.NAMESPACE_MESSAGE;
import static org.finos.calm.resources.ResourceValidationConstants.NAMESPACE_REGEX;
import static org.finos.calm.resources.ResourceValidationConstants.QUERY_PARAM_NO_WHITESPACE_MESSAGE;
import static org.finos.calm.resources.ResourceValidationConstants.QUERY_PARAM_NO_WHITESPACE_REGEX;

/**
* Resource for managing decorators in a given namespace
*/
@Path("/calm/namespaces")
public class DecoratorResource {

private final DecoratorStore decoratorStore;
private final Logger logger = LoggerFactory.getLogger(DecoratorResource.class);

@Inject
public DecoratorResource(DecoratorStore decoratorStore) {
this.decoratorStore = decoratorStore;
}

/**
* Retrieve a list of decorator IDs in a given namespace with optional filtering
*
* @param namespace the namespace to retrieve decorators for
* @param target optional target path to filter by (e.g., "/calm/namespaces/finos/architectures/1/versions/1-0-0")
* @param type optional decorator type to filter by (e.g., "deployment", "observability")
* @return a list of decorator IDs matching the criteria
*/
@GET
@Path("{namespace}/decorators")
@Produces(MediaType.APPLICATION_JSON)
@Operation(
summary = "Retrieve decorators in a given namespace",
description = "Decorator IDs stored in a given namespace, optionally filtered by target and/or type"
)
@PermittedScopes({CalmHubScopes.ARCHITECTURES_ALL, CalmHubScopes.ARCHITECTURES_READ})
public Response getDecoratorsForNamespace(
@PathParam("namespace") @Pattern(regexp = NAMESPACE_REGEX, message = NAMESPACE_MESSAGE) String namespace,
@QueryParam("target") @Size(max = 500) @Pattern(regexp = QUERY_PARAM_NO_WHITESPACE_REGEX, message = QUERY_PARAM_NO_WHITESPACE_MESSAGE) String target,
@QueryParam("type") @Size(max = 100) @Pattern(regexp = QUERY_PARAM_NO_WHITESPACE_REGEX, message = QUERY_PARAM_NO_WHITESPACE_MESSAGE) String type
) {
try {
return Response.ok(new ValueWrapper<>(decoratorStore.getDecoratorsForNamespace(namespace, target, type))).build();
} catch (NamespaceNotFoundException e) {
logger.error("Invalid namespace [{}] when retrieving decorators", namespace, e);
return CalmResourceErrorResponses.invalidNamespaceResponse(namespace);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class ResourceValidationConstants {
public static final String DOMAIN_NAME_MESSAGE = "domain name must match pattern '^[A-Za-z0-9-]+$'";
public static final String VERSION_REGEX = "^(0|[1-9][0-9]*)[-.]?(0|[1-9][0-9]*)[-.]?(0|[1-9][0-9]*)$";
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]*)$'";
public static final String QUERY_PARAM_NO_WHITESPACE_REGEX = "^[A-Za-z0-9_/-]+$";
public static final String QUERY_PARAM_NO_WHITESPACE_MESSAGE = "Query parameter must contain only alphanumeric characters, hyphens, underscores, and forward slashes";
public static final PolicyFactory STRICT_SANITIZATION_POLICY = new HtmlPolicyBuilder().toFactory();

}
22 changes: 22 additions & 0 deletions calm-hub/src/main/java/org/finos/calm/store/DecoratorStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.finos.calm.store;

import org.finos.calm.domain.exception.NamespaceNotFoundException;

import java.util.List;

/**
* Interface for decorator storage operations.
*/
public interface DecoratorStore {

/**
* Retrieve decorator IDs for a given namespace with optional filtering.
*
* @param namespace the namespace to retrieve decorators for
* @param target optional target path to filter by (e.g., "/calm/namespaces/finos/architectures/1/versions/1-0-0")
* @param type optional decorator type to filter by (e.g., "deployment", "observability")
* @return a list of decorator IDs matching the criteria
* @throws NamespaceNotFoundException if the namespace does not exist
*/
List<Integer> getDecoratorsForNamespace(String namespace, String target, String type) throws NamespaceNotFoundException;
}
Loading
Loading