Skip to content

Commit 9b141cf

Browse files
Share Content Pack Entities after installation (#23454)
* Minor refactorings & cleanups. * Implementing hook called after content pack installation to share created entities. * Remove unused dependencies in test. * Reusing `CreateEntityRequest`. * Adding license headers. * Adding test assertion to check if hooks have been executed. * Adding changelog snippet. * Adding upgrade note for API change. * Bail out early if no sharing has been specified. * Removing `ContentPackInstallationHook`, extracting `ContentPackEntityResolver`, adjusting tests. * Improving test. * Do not try to generate GRNs with unsupported type. --------- Co-authored-by: Maxwell <98284293+kodjo-anipah@users.noreply.github.com>
1 parent d09dda3 commit 9b141cf

File tree

19 files changed

+425
-235
lines changed

19 files changed

+425
-235
lines changed

UPGRADING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ is now used as the primary color for elements like buttons and badges in the UI.
5959
- Sigma rules
6060
- Event procedure
6161
- Event step
62+
- Content Pack installation
6263

6364
<br> For example, the request payload to create a stream might now look like this:
6465

changelog/unreleased/pr-23454.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type = "a"
2+
message = 'Allow sharing Content Pack Entities after installation'
3+
4+
issues = []
5+
pulls = ["23454"]

graylog2-server/src/main/java/org/graylog/grn/GRNRegistry.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,13 @@ private String toKey(String type) {
188188

189189
return type.trim().toLowerCase(Locale.US);
190190
}
191+
192+
/**
193+
* Checks if a given type is supported by this registry.
194+
*
195+
* @param type the type to check for support
196+
*/
197+
public boolean supportsType(String type) {
198+
return REGISTRY.containsKey(toKey(type));
199+
}
191200
}

graylog2-server/src/main/java/org/graylog/security/entities/DefaultEntityDependencyResolver.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import org.graylog.grn.GRNType;
2626
import org.graylog.security.DBGrantService;
2727
import org.graylog.security.shares.Grantee;
28-
import org.graylog2.contentpacks.ContentPackService;
28+
import org.graylog2.contentpacks.ContentPackEntityResolver;
2929
import org.graylog2.contentpacks.model.ModelId;
3030
import org.graylog2.contentpacks.model.ModelType;
3131
import org.graylog2.contentpacks.model.ModelTypes;
@@ -41,17 +41,17 @@
4141
import static com.google.common.base.MoreObjects.firstNonNull;
4242

4343
public class DefaultEntityDependencyResolver implements EntityDependencyResolver {
44-
private final ContentPackService contentPackService;
44+
private final ContentPackEntityResolver contentPackEntityResolver;
4545
private final GRNRegistry grnRegistry;
4646
private final GRNDescriptorService descriptorService;
4747
private final DBGrantService grantService;
4848

4949
@Inject
50-
public DefaultEntityDependencyResolver(ContentPackService contentPackService,
50+
public DefaultEntityDependencyResolver(ContentPackEntityResolver contentPackEntityResolver,
5151
GRNRegistry grnRegistry,
5252
GRNDescriptorService descriptorService,
5353
DBGrantService grantService) {
54-
this.contentPackService = contentPackService;
54+
this.contentPackEntityResolver = contentPackEntityResolver;
5555
this.grnRegistry = grnRegistry;
5656
this.descriptorService = descriptorService;
5757
this.grantService = grantService;
@@ -66,7 +66,7 @@ public ImmutableSet<EntityDescriptor> resolve(GRN entity) {
6666
protected ImmutableSet<EntityDescriptor> resolve(Collection<GRN> entities) {
6767
final var cpDescriptors = entities.stream().map(DefaultEntityDependencyResolver::toContentPackEntityDescriptor)
6868
.collect(Collectors.toUnmodifiableSet());
69-
final var dependencyGraph = contentPackService.resolveEntityDependencyGraph(cpDescriptors);
69+
final var dependencyGraph = contentPackEntityResolver.resolveEntityDependencyGraph(cpDescriptors);
7070
final var dependencies = dependencyGraph.nodes().stream()
7171
.filter(dependency -> !cpDescriptors.contains(dependency)) // Don't include the given entity in dependencies
7272
// Workaround to ignore outputs as dependencies of streams.
@@ -118,7 +118,7 @@ private String getExcerptTitle(GRN entity, ImmutableMap<GRN, Optional<String>> e
118118

119119
private ImmutableMap<GRN, Optional<String>> entityExcerpts() {
120120
// TODO: Replace entity excerpt usage with GRNDescriptors once we implemented GRN descriptors for every entity
121-
return contentPackService.listAllEntityExcerpts().stream()
121+
return contentPackEntityResolver.listAllEntityExcerpts().stream()
122122
// TODO: Use the GRNRegistry instead of manually building a GRN. Requires all entity types to be in the registry.
123123
.collect(ImmutableMap.toImmutableMap(e -> GRNType.create(e.type().name()).newGRNBuilder().entity(e.id().id()).build(),
124124
v -> Optional.ofNullable(v.title())));

graylog2-server/src/main/java/org/graylog/security/shares/EntityShareRequest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
@AutoValue
3535
@JsonAutoDetect
3636
public abstract class EntityShareRequest {
37+
public static final EntityShareRequest EMPTY = empty();
3738

3839
public static final String SELECTED_GRANTEE_CAPABILITIES = "selected_grantee_capabilities";
3940
public static final String SELECTED_COLLECTIONS = "selected_collections";
@@ -64,6 +65,10 @@ public static EntityShareRequest create(
6465
return new AutoValue_EntityShareRequest(Optional.ofNullable(capabilities), Optional.ofNullable(collections));
6566
}
6667

68+
public static EntityShareRequest empty() {
69+
return create(Map.of(), List.of());
70+
}
71+
6772
public static EntityShareRequest create(
6873
Map<GRN, Capability> selectedGranteeCapabilities) {
6974
return create(selectedGranteeCapabilities, null);
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright (C) 2020 Graylog, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the Server Side Public License, version 1,
6+
* as published by MongoDB, Inc.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* Server Side Public License for more details.
12+
*
13+
* You should have received a copy of the Server Side Public License
14+
* along with this program. If not, see
15+
* <http://www.mongodb.com/licensing/server-side-public-license>.
16+
*/
17+
package org.graylog2.contentpacks;
18+
19+
import com.google.common.collect.ImmutableSet;
20+
import com.google.common.graph.ElementOrder;
21+
import com.google.common.graph.Graph;
22+
import com.google.common.graph.GraphBuilder;
23+
import com.google.common.graph.MutableGraph;
24+
import jakarta.inject.Inject;
25+
import org.graylog2.contentpacks.facades.EntityWithExcerptFacade;
26+
import org.graylog2.contentpacks.facades.UnsupportedEntityFacade;
27+
import org.graylog2.contentpacks.model.ModelType;
28+
import org.graylog2.contentpacks.model.entities.Entity;
29+
import org.graylog2.contentpacks.model.entities.EntityDescriptor;
30+
import org.graylog2.contentpacks.model.entities.EntityExcerpt;
31+
import org.graylog2.utilities.Graphs;
32+
import org.slf4j.Logger;
33+
import org.slf4j.LoggerFactory;
34+
35+
import java.util.Collection;
36+
import java.util.HashSet;
37+
import java.util.Map;
38+
import java.util.Set;
39+
import java.util.stream.Collectors;
40+
41+
public class ContentPackEntityResolver {
42+
private static final Logger LOG = LoggerFactory.getLogger(ContentPackEntityResolver.class);
43+
private final Map<ModelType, EntityWithExcerptFacade<?, ?>> entityFacades;
44+
45+
@Inject
46+
public ContentPackEntityResolver(Map<ModelType, EntityWithExcerptFacade<?, ?>> entityFacades) {
47+
this.entityFacades = entityFacades;
48+
}
49+
50+
public Set<EntityExcerpt> listAllEntityExcerpts() {
51+
final ImmutableSet.Builder<EntityExcerpt> entityIndexBuilder = ImmutableSet.builder();
52+
entityFacades.values().forEach(facade -> entityIndexBuilder.addAll(facade.listEntityExcerpts()));
53+
return entityIndexBuilder.build();
54+
}
55+
56+
public Map<String, EntityExcerpt> getEntityExcerpts() {
57+
return listAllEntityExcerpts().stream().collect(Collectors.toMap(x -> x.id().id(), x -> x));
58+
}
59+
60+
public ImmutableSet<Entity> collectEntities(Collection<EntityDescriptor> resolvedEntities) {
61+
// It's important to only compute the EntityDescriptor IDs once per #collectEntities call! Otherwise we
62+
// will get broken references between the entities.
63+
final EntityDescriptorIds entityDescriptorIds = EntityDescriptorIds.of(resolvedEntities);
64+
65+
final ImmutableSet.Builder<Entity> entities = ImmutableSet.builder();
66+
for (EntityDescriptor entityDescriptor : resolvedEntities) {
67+
if (EntityDescriptorIds.isSystemStreamDescriptor(entityDescriptor)) {
68+
continue;
69+
}
70+
final EntityWithExcerptFacade<?, ?> facade = entityFacades.getOrDefault(entityDescriptor.type(), UnsupportedEntityFacade.INSTANCE);
71+
72+
facade.exportEntity(entityDescriptor, entityDescriptorIds).ifPresent(entities::add);
73+
}
74+
75+
return entities.build();
76+
}
77+
78+
public Set<EntityDescriptor> resolveEntities(Collection<EntityDescriptor> unresolvedEntities) {
79+
return resolveEntityDependencyGraph(unresolvedEntities).nodes();
80+
}
81+
82+
public Graph<EntityDescriptor> resolveEntityDependencyGraph(Collection<EntityDescriptor> unresolvedEntities) {
83+
final MutableGraph<EntityDescriptor> dependencyGraph = GraphBuilder.directed()
84+
.allowsSelfLoops(false)
85+
.nodeOrder(ElementOrder.insertion())
86+
.build();
87+
unresolvedEntities.forEach(dependencyGraph::addNode);
88+
89+
final HashSet<EntityDescriptor> resolvedEntities = new HashSet<>();
90+
final MutableGraph<EntityDescriptor> finalDependencyGraph = resolveDependencyGraph(dependencyGraph, resolvedEntities);
91+
92+
LOG.debug("Final dependency graph: {}", finalDependencyGraph);
93+
94+
return finalDependencyGraph;
95+
}
96+
97+
private MutableGraph<EntityDescriptor> resolveDependencyGraph(Graph<EntityDescriptor> dependencyGraph, Set<EntityDescriptor> resolvedEntities) {
98+
final MutableGraph<EntityDescriptor> mutableGraph = GraphBuilder.from(dependencyGraph).build();
99+
Graphs.merge(mutableGraph, dependencyGraph);
100+
101+
for (EntityDescriptor entityDescriptor : dependencyGraph.nodes()) {
102+
LOG.debug("Resolving entity {}", entityDescriptor);
103+
if (resolvedEntities.contains(entityDescriptor)) {
104+
LOG.debug("Entity {} already resolved, skipping.", entityDescriptor);
105+
continue;
106+
}
107+
108+
final EntityWithExcerptFacade<?, ?> facade = entityFacades.getOrDefault(entityDescriptor.type(), UnsupportedEntityFacade.INSTANCE);
109+
final Graph<EntityDescriptor> graph = facade.resolveNativeEntity(entityDescriptor);
110+
LOG.trace("Dependencies of entity {}: {}", entityDescriptor, graph);
111+
112+
Graphs.merge(mutableGraph, graph);
113+
LOG.trace("New dependency graph: {}", mutableGraph);
114+
115+
resolvedEntities.add(entityDescriptor);
116+
final Graph<EntityDescriptor> result = resolveDependencyGraph(mutableGraph, resolvedEntities);
117+
Graphs.merge(mutableGraph, result);
118+
}
119+
120+
return mutableGraph;
121+
}
122+
}

0 commit comments

Comments
 (0)