NIFI-10915 Add additionalDetails content to runtime manifest

- Update manifests assembly to include the additionaDetails.html files
- Update C2 ExtensionDefinition to add a new field for additional details content
- Update manifest generation to read the additional details and pass through to C2 runtime manifest
- Switch to boolean to indicate presence of additional details, instead of including content

Add additionalDetails files to nifi-runtime-manifest artifact, organized by group/artifact/version/type

Signed-off-by: Matthew Burgess <mattyb149@apache.org>

This closes #6767
This commit is contained in:
Bryan Bende 2022-12-05 17:26:12 -05:00 committed by Matthew Burgess
parent b744fac479
commit 334c9db9c3
No known key found for this signature in database
GPG Key ID: 05D3DEB8126DAD24
11 changed files with 241 additions and 38 deletions

View File

@ -47,6 +47,8 @@ public class ExtensionComponent extends DefinedType {
private Stateful stateful; private Stateful stateful;
private boolean additionalDetails;
@ApiModelProperty("The build metadata for this component") @ApiModelProperty("The build metadata for this component")
public BuildInfo getBuildInfo() { public BuildInfo getBuildInfo() {
return buildInfo; return buildInfo;
@ -120,6 +122,7 @@ public class ExtensionComponent extends DefinedType {
this.explicitRestrictions = explicitRestrictions; this.explicitRestrictions = explicitRestrictions;
} }
@ApiModelProperty("Indicates if the component stores state")
public Stateful getStateful() { public Stateful getStateful() {
return stateful; return stateful;
} }
@ -128,6 +131,15 @@ public class ExtensionComponent extends DefinedType {
this.stateful = stateful; this.stateful = stateful;
} }
@ApiModelProperty("Indicates if the component has additional details documentation")
public boolean isAdditionalDetails() {
return additionalDetails;
}
public void setAdditionalDetails(boolean additionalDetails) {
this.additionalDetails = additionalDetails;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) {

View File

@ -32,16 +32,13 @@ language governing permissions and limitations under the License. -->
<phase>generate-resources</phase> <phase>generate-resources</phase>
<configuration> <configuration>
<includeTypes>nar</includeTypes> <includeTypes>nar</includeTypes>
<includes>**/extension-manifest.xml</includes> <includes>**/docs/**</includes>
<excludeTransitive>false</excludeTransitive> <excludeTransitive>false</excludeTransitive>
<outputDirectory>${project.build.directory}/extension-manifests</outputDirectory> <outputDirectory>${project.build.directory}/extension-manifests</outputDirectory>
<useSubDirectoryPerArtifact>true</useSubDirectoryPerArtifact> <useSubDirectoryPerArtifact>true</useSubDirectoryPerArtifact>
<stripClassifier>true</stripClassifier> <stripClassifier>true</stripClassifier>
<stripVersion>true</stripVersion> <stripVersion>true</stripVersion>
<silent>true</silent> <silent>true</silent>
<fileMappers>
<org.codehaus.plexus.components.io.filemappers.FlattenFileMapper />
</fileMappers>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>

View File

@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.runtime.manifest;
import org.apache.nifi.extension.manifest.ExtensionManifest;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
public class ExtensionManifestContainer {
private final ExtensionManifest manifest;
private final Map<String, String> additionalDetails;
public ExtensionManifestContainer(final ExtensionManifest manifest) {
this(manifest, null);
}
public ExtensionManifestContainer(final ExtensionManifest manifest, final Map<String, String> additionalDetails) {
this.manifest = Objects.requireNonNull(manifest);
this.additionalDetails = Collections.unmodifiableMap(additionalDetails == null
? Collections.emptyMap() : new LinkedHashMap<>(additionalDetails));
}
public ExtensionManifest getManifest() {
return manifest;
}
public Map<String, String> getAdditionalDetails() {
return additionalDetails;
}
}

View File

@ -16,8 +16,6 @@
*/ */
package org.apache.nifi.runtime.manifest; package org.apache.nifi.runtime.manifest;
import org.apache.nifi.extension.manifest.ExtensionManifest;
import java.util.List; import java.util.List;
/** /**
@ -25,6 +23,6 @@ import java.util.List;
*/ */
public interface ExtensionManifestProvider { public interface ExtensionManifestProvider {
List<ExtensionManifest> getExtensionManifests(); List<ExtensionManifestContainer> getExtensionManifests();
} }

View File

@ -20,7 +20,6 @@ import org.apache.nifi.c2.protocol.component.api.BuildInfo;
import org.apache.nifi.c2.protocol.component.api.Bundle; import org.apache.nifi.c2.protocol.component.api.Bundle;
import org.apache.nifi.c2.protocol.component.api.RuntimeManifest; import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
import org.apache.nifi.c2.protocol.component.api.SchedulingDefaults; import org.apache.nifi.c2.protocol.component.api.SchedulingDefaults;
import org.apache.nifi.extension.manifest.ExtensionManifest;
/** /**
* Builder for creating a RuntimeManifest. * Builder for creating a RuntimeManifest.
@ -57,7 +56,7 @@ public interface RuntimeManifestBuilder {
* @param extensionManifest the extension manifest to add * @param extensionManifest the extension manifest to add
* @return the builder * @return the builder
*/ */
RuntimeManifestBuilder addBundle(ExtensionManifest extensionManifest); RuntimeManifestBuilder addBundle(ExtensionManifestContainer extensionManifest);
/** /**
* Adds a Bundle for each of the given ExtensionManifests. * Adds a Bundle for each of the given ExtensionManifests.
@ -65,7 +64,7 @@ public interface RuntimeManifestBuilder {
* @param extensionManifests the extension manifests to add * @param extensionManifests the extension manifests to add
* @return the builder * @return the builder
*/ */
RuntimeManifestBuilder addBundles(Iterable<ExtensionManifest> extensionManifests); RuntimeManifestBuilder addBundles(Iterable<ExtensionManifestContainer> extensionManifests);
/** /**
* Adds the given Bundle. * Adds the given Bundle.

View File

@ -16,8 +16,9 @@
*/ */
package org.apache.nifi.runtime.manifest.impl; package org.apache.nifi.runtime.manifest.impl;
import org.apache.nifi.extension.manifest.parser.ExtensionManifestParser;
import org.apache.nifi.extension.manifest.ExtensionManifest; import org.apache.nifi.extension.manifest.ExtensionManifest;
import org.apache.nifi.extension.manifest.parser.ExtensionManifestParser;
import org.apache.nifi.runtime.manifest.ExtensionManifestContainer;
import org.apache.nifi.runtime.manifest.ExtensionManifestProvider; import org.apache.nifi.runtime.manifest.ExtensionManifestProvider;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -26,8 +27,12 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* ExtensionManifestProvider that loads extension manifests from a directory where the nifi-assembly-manifests * ExtensionManifestProvider that loads extension manifests from a directory where the nifi-assembly-manifests
@ -46,7 +51,7 @@ public class DirectoryExtensionManifestProvider implements ExtensionManifestProv
} }
@Override @Override
public List<ExtensionManifest> getExtensionManifests() { public List<ExtensionManifestContainer> getExtensionManifests() {
if (!baseDir.exists()) { if (!baseDir.exists()) {
throw new IllegalArgumentException("The specified manifest directory does not exist"); throw new IllegalArgumentException("The specified manifest directory does not exist");
} }
@ -56,18 +61,22 @@ public class DirectoryExtensionManifestProvider implements ExtensionManifestProv
LOGGER.info("Loading extension manifests from: {}", baseDir.getAbsolutePath()); LOGGER.info("Loading extension manifests from: {}", baseDir.getAbsolutePath());
final List<ExtensionManifest> extensionManifests = new ArrayList<>(); final List<ExtensionManifestContainer> extensionManifests = new ArrayList<>();
for (final File manifestDir : baseDir.listFiles()) { for (final File manifestDir : baseDir.listFiles()) {
if (!manifestDir.isDirectory()) { if (!manifestDir.isDirectory()) {
LOGGER.debug("Skipping [{}], not a directory...", manifestDir.getAbsolutePath()); LOGGER.debug("Skipping [{}], not a directory...", manifestDir.getAbsolutePath());
continue; continue;
} }
final File manifestFile = new File(manifestDir, "extension-manifest.xml"); final File manifestFile = new File(manifestDir, "META-INF/docs/extension-manifest.xml");
LOGGER.debug("Loading extension manifest file [{}]", manifestFile.getAbsolutePath()); LOGGER.debug("Loading extension manifest file [{}]", manifestFile.getAbsolutePath());
final ExtensionManifest extensionManifest = loadExtensionManifest(manifestFile); final ExtensionManifest extensionManifest = loadExtensionManifest(manifestFile);
extensionManifests.add(extensionManifest); final Map<String, String> additionalDetails = loadAdditionalDetails(manifestDir);
final ExtensionManifestContainer container = new ExtensionManifestContainer(extensionManifest, additionalDetails);
extensionManifests.add(container);
LOGGER.debug("Successfully loaded extension manifest for [{}-{}-{}]", LOGGER.debug("Successfully loaded extension manifest for [{}-{}-{}]",
extensionManifest.getGroupId(), extensionManifest.getArtifactId(), extensionManifest.getVersion()); extensionManifest.getGroupId(), extensionManifest.getArtifactId(), extensionManifest.getVersion());
} }
@ -83,4 +92,40 @@ public class DirectoryExtensionManifestProvider implements ExtensionManifestProv
throw new RuntimeException("Unable to load extension manifest: " + manifestFile.getAbsolutePath(), ioException); throw new RuntimeException("Unable to load extension manifest: " + manifestFile.getAbsolutePath(), ioException);
} }
} }
private Map<String, String> loadAdditionalDetails(final File manifestDir) {
final Map<String, String> additionalDetailsMap = new LinkedHashMap<>();
final File additionalDetailsDir = new File(manifestDir, "META-INF/docs/additional-details");
if (!additionalDetailsDir.exists()) {
LOGGER.debug("No additional-details directory found under [{}]", manifestDir.getAbsolutePath());
return additionalDetailsMap;
}
for (final File additionalDetailsTypeDir : additionalDetailsDir.listFiles()) {
if (!additionalDetailsTypeDir.isDirectory()) {
LOGGER.debug("Skipping [{}], not a directory...", additionalDetailsTypeDir.getAbsolutePath());
continue;
}
final File additionalDetailsFile = new File(additionalDetailsTypeDir, "additionalDetails.html");
if (!additionalDetailsFile.exists()) {
LOGGER.debug("No additionalDetails.html found under [{}]", additionalDetailsTypeDir.getAbsolutePath());
continue;
}
try {
final String typeName = additionalDetailsTypeDir.getName();
final byte[] additionalDetailsBytes = Files.readAllBytes(additionalDetailsFile.toPath());
LOGGER.debug("Added additionalDetails for {} from {}", typeName, additionalDetailsFile.getAbsolutePath());
additionalDetailsMap.put(typeName, new String(additionalDetailsBytes, StandardCharsets.UTF_8));
} catch (final IOException e) {
throw new RuntimeException("Unable to load additional details content for "
+ additionalDetailsFile.getAbsolutePath() + " due to: " + e.getMessage(), e);
}
}
return additionalDetailsMap;
}
} }

View File

@ -21,8 +21,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.ObjectWriter;
import org.apache.nifi.c2.protocol.component.api.BuildInfo; import org.apache.nifi.c2.protocol.component.api.BuildInfo;
import org.apache.nifi.c2.protocol.component.api.RuntimeManifest; import org.apache.nifi.c2.protocol.component.api.RuntimeManifest;
import org.apache.nifi.extension.manifest.Extension;
import org.apache.nifi.extension.manifest.ExtensionManifest;
import org.apache.nifi.extension.manifest.parser.ExtensionManifestParser; import org.apache.nifi.extension.manifest.parser.ExtensionManifestParser;
import org.apache.nifi.extension.manifest.parser.jaxb.JAXBExtensionManifestParser; import org.apache.nifi.extension.manifest.parser.jaxb.JAXBExtensionManifestParser;
import org.apache.nifi.runtime.manifest.ExtensionManifestContainer;
import org.apache.nifi.runtime.manifest.ExtensionManifestProvider; import org.apache.nifi.runtime.manifest.ExtensionManifestProvider;
import org.apache.nifi.runtime.manifest.RuntimeManifestSerializer; import org.apache.nifi.runtime.manifest.RuntimeManifestSerializer;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -34,8 +37,12 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
/** /**
@ -93,12 +100,14 @@ public class RuntimeManifestGenerator {
buildInfo.setTimestamp(buildTimestampMillis); buildInfo.setTimestamp(buildTimestampMillis);
buildInfo.setCompiler(buildJdkVendor + " " + buildJdk); buildInfo.setCompiler(buildJdkVendor + " " + buildJdk);
final List<ExtensionManifestContainer> extensionsManifests = extensionManifestProvider.getExtensionManifests();
final RuntimeManifest runtimeManifest = new StandardRuntimeManifestBuilder() final RuntimeManifest runtimeManifest = new StandardRuntimeManifestBuilder()
.identifier(runtimeManifestId) .identifier(runtimeManifestId)
.version(runtimeVersion) .version(runtimeVersion)
.runtimeType("nifi") .runtimeType("nifi")
.buildInfo(buildInfo) .buildInfo(buildInfo)
.addBundles(extensionManifestProvider.getExtensionManifests()) .addBundles(extensionsManifests)
.schedulingDefaults(SchedulingDefaultsFactory.getNifiSchedulingDefaults()) .schedulingDefaults(SchedulingDefaultsFactory.getNifiSchedulingDefaults())
.build(); .build();
@ -106,6 +115,30 @@ public class RuntimeManifestGenerator {
try (final OutputStream outputStream = new FileOutputStream(runtimeManifestFile)) { try (final OutputStream outputStream = new FileOutputStream(runtimeManifestFile)) {
runtimeManifestSerializer.write(runtimeManifest, outputStream); runtimeManifestSerializer.write(runtimeManifest, outputStream);
} }
final File docsDir = new File(runtimeManifestFile.getParent(), "docs");
docsDir.mkdirs();
for (final ExtensionManifestContainer manifestContainer : extensionsManifests) {
final ExtensionManifest extensionManifest = manifestContainer.getManifest();
final Map<String, String> additionalDetailsMap = manifestContainer.getAdditionalDetails();
final File bundleDir = new File(docsDir, extensionManifest.getGroupId()
+ "/" + extensionManifest.getArtifactId()
+ "/" + extensionManifest.getVersion());
for (final Extension extension : extensionManifest.getExtensions()) {
final String extensionType = extension.getName();
final File extensionDir = new File(bundleDir, extensionType);
final String additionalDetails = additionalDetailsMap.get(extensionType);
if (additionalDetails != null) {
extensionDir.mkdirs();
final File additionalDetailsFile = new File(extensionDir, "additionalDetails.html");
Files.write(additionalDetailsFile.toPath(), additionalDetails.getBytes(StandardCharsets.UTF_8));
}
}
}
} }
private ExtensionManifestProvider createExtensionManifestProvider() { private ExtensionManifestProvider createExtensionManifestProvider() {

View File

@ -52,6 +52,7 @@ import org.apache.nifi.extension.manifest.Restricted;
import org.apache.nifi.extension.manifest.Stateful; import org.apache.nifi.extension.manifest.Stateful;
import org.apache.nifi.logging.LogLevel; import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.runtime.manifest.ComponentManifestBuilder; import org.apache.nifi.runtime.manifest.ComponentManifestBuilder;
import org.apache.nifi.runtime.manifest.ExtensionManifestContainer;
import org.apache.nifi.runtime.manifest.RuntimeManifestBuilder; import org.apache.nifi.runtime.manifest.RuntimeManifestBuilder;
import org.apache.nifi.scheduling.SchedulingStrategy; import org.apache.nifi.scheduling.SchedulingStrategy;
@ -105,7 +106,12 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder {
} }
@Override @Override
public RuntimeManifestBuilder addBundle(final ExtensionManifest extensionManifest) { public RuntimeManifestBuilder addBundle(final ExtensionManifestContainer extensionManifestContainer) {
if (extensionManifestContainer == null) {
throw new IllegalArgumentException("Extension manifest container is required");
}
final ExtensionManifest extensionManifest = extensionManifestContainer.getManifest();
if (extensionManifest == null) { if (extensionManifest == null) {
throw new IllegalArgumentException("Extension manifest is required"); throw new IllegalArgumentException("Extension manifest is required");
} }
@ -125,8 +131,12 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder {
bundle.setVersion(extensionManifest.getVersion()); bundle.setVersion(extensionManifest.getVersion());
if (extensionManifest.getExtensions() != null) { if (extensionManifest.getExtensions() != null) {
final Map<String, String> additionalDetailsMap = extensionManifestContainer.getAdditionalDetails();
final ComponentManifestBuilder componentManifestBuilder = new StandardComponentManifestBuilder(); final ComponentManifestBuilder componentManifestBuilder = new StandardComponentManifestBuilder();
extensionManifest.getExtensions().forEach(extension -> addExtension(extensionManifest, extension, componentManifestBuilder)); extensionManifest.getExtensions().forEach(extension -> {
final String additionalDetails = additionalDetailsMap.get(extension.getName());
addExtension(extensionManifest, extension, additionalDetails, componentManifestBuilder);
});
bundle.setComponentManifest(componentManifestBuilder.build()); bundle.setComponentManifest(componentManifestBuilder.build());
} }
bundles.add(bundle); bundles.add(bundle);
@ -135,7 +145,7 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder {
} }
@Override @Override
public RuntimeManifestBuilder addBundles(final Iterable<ExtensionManifest> extensionManifests) { public RuntimeManifestBuilder addBundles(final Iterable<ExtensionManifestContainer> extensionManifests) {
extensionManifests.forEach(em -> addBundle(em)); extensionManifests.forEach(em -> addBundle(em));
return this; return this;
} }
@ -167,30 +177,32 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder {
return runtimeManifest; return runtimeManifest;
} }
private void addExtension(final ExtensionManifest extensionManifest, final Extension extension, final ComponentManifestBuilder componentManifestBuilder) { private void addExtension(final ExtensionManifest extensionManifest, final Extension extension, final String additionalDetails,
final ComponentManifestBuilder componentManifestBuilder) {
if (extension == null) { if (extension == null) {
throw new IllegalArgumentException("Extension cannot be null"); throw new IllegalArgumentException("Extension cannot be null");
} }
switch(extension.getType()) { switch(extension.getType()) {
case PROCESSOR: case PROCESSOR:
addProcessorDefinition(extensionManifest, extension, componentManifestBuilder); addProcessorDefinition(extensionManifest, extension, additionalDetails, componentManifestBuilder);
break; break;
case CONTROLLER_SERVICE: case CONTROLLER_SERVICE:
addControllerServiceDefinition(extensionManifest, extension, componentManifestBuilder); addControllerServiceDefinition(extensionManifest, extension, additionalDetails, componentManifestBuilder);
break; break;
case REPORTING_TASK: case REPORTING_TASK:
addReportingTaskDefinition(extensionManifest, extension, componentManifestBuilder); addReportingTaskDefinition(extensionManifest, extension, additionalDetails, componentManifestBuilder);
break; break;
default: default:
throw new IllegalArgumentException("Unknown extension type: " + extension.getType()); throw new IllegalArgumentException("Unknown extension type: " + extension.getType());
} }
} }
private void addProcessorDefinition(final ExtensionManifest extensionManifest, final Extension extension, final ComponentManifestBuilder componentManifestBuilder) { private void addProcessorDefinition(final ExtensionManifest extensionManifest, final Extension extension, final String additionalDetails,
final ComponentManifestBuilder componentManifestBuilder) {
final ProcessorDefinition processorDefinition = new ProcessorDefinition(); final ProcessorDefinition processorDefinition = new ProcessorDefinition();
populateDefinedType(extensionManifest, extension, processorDefinition); populateDefinedType(extensionManifest, extension, processorDefinition);
populateExtensionComponent(extensionManifest, extension, processorDefinition); populateExtensionComponent(extensionManifest, extension, additionalDetails, processorDefinition);
populateConfigurableComponent(extension, processorDefinition); populateConfigurableComponent(extension, processorDefinition);
// processor specific fields // processor specific fields
@ -279,18 +291,20 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder {
return componentRelationships; return componentRelationships;
} }
private void addControllerServiceDefinition(final ExtensionManifest extensionManifest, final Extension extension, final ComponentManifestBuilder componentManifestBuilder) { private void addControllerServiceDefinition(final ExtensionManifest extensionManifest, final Extension extension, final String additionalDetails,
final ComponentManifestBuilder componentManifestBuilder) {
final ControllerServiceDefinition controllerServiceDefinition = new ControllerServiceDefinition(); final ControllerServiceDefinition controllerServiceDefinition = new ControllerServiceDefinition();
populateDefinedType(extensionManifest, extension, controllerServiceDefinition); populateDefinedType(extensionManifest, extension, controllerServiceDefinition);
populateExtensionComponent(extensionManifest, extension, controllerServiceDefinition); populateExtensionComponent(extensionManifest, extension, additionalDetails, controllerServiceDefinition);
populateConfigurableComponent(extension, controllerServiceDefinition); populateConfigurableComponent(extension, controllerServiceDefinition);
componentManifestBuilder.addControllerService(controllerServiceDefinition); componentManifestBuilder.addControllerService(controllerServiceDefinition);
} }
private void addReportingTaskDefinition(final ExtensionManifest extensionManifest, final Extension extension, final ComponentManifestBuilder componentManifestBuilder) { private void addReportingTaskDefinition(final ExtensionManifest extensionManifest, final Extension extension, final String additionalDetails,
final ComponentManifestBuilder componentManifestBuilder) {
final ReportingTaskDefinition reportingTaskDefinition = new ReportingTaskDefinition(); final ReportingTaskDefinition reportingTaskDefinition = new ReportingTaskDefinition();
populateDefinedType(extensionManifest, extension, reportingTaskDefinition); populateDefinedType(extensionManifest, extension, reportingTaskDefinition);
populateDefinedType(extensionManifest, extension, reportingTaskDefinition); populateExtensionComponent(extensionManifest, extension, additionalDetails, reportingTaskDefinition);
populateConfigurableComponent(extension, reportingTaskDefinition); populateConfigurableComponent(extension, reportingTaskDefinition);
final List<String> schedulingStrategies = new ArrayList<>(); final List<String> schedulingStrategies = new ArrayList<>();
@ -326,7 +340,8 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder {
definedType.setVersion(extensionManifest.getVersion()); definedType.setVersion(extensionManifest.getVersion());
} }
private void populateExtensionComponent(final ExtensionManifest extensionManifest, final Extension extension, final ExtensionComponent extensionComponent) { private void populateExtensionComponent(final ExtensionManifest extensionManifest, final Extension extension, final String additionalDetails,
final ExtensionComponent extensionComponent) {
final org.apache.nifi.extension.manifest.BuildInfo buildInfo = extensionManifest.getBuildInfo(); final org.apache.nifi.extension.manifest.BuildInfo buildInfo = extensionManifest.getBuildInfo();
if (buildInfo != null) { if (buildInfo != null) {
final BuildInfo componentBuildInfo = new BuildInfo(); final BuildInfo componentBuildInfo = new BuildInfo();
@ -377,7 +392,10 @@ public class StandardRuntimeManifestBuilder implements RuntimeManifestBuilder {
); );
extensionComponent.setStateful(componentStateful); extensionComponent.setStateful(componentStateful);
} }
}
if (additionalDetails != null) {
extensionComponent.setAdditionalDetails(true);
} }
} }

View File

@ -107,6 +107,7 @@ class TestRuntimeManifest {
assertFalse(listHdfsDefinition.getSupportsDynamicProperties()); assertFalse(listHdfsDefinition.getSupportsDynamicProperties());
assertFalse(listHdfsDefinition.getSupportsDynamicRelationships()); assertFalse(listHdfsDefinition.getSupportsDynamicRelationships());
assertEquals(InputRequirement.Requirement.INPUT_FORBIDDEN, listHdfsDefinition.getInputRequirement()); assertEquals(InputRequirement.Requirement.INPUT_FORBIDDEN, listHdfsDefinition.getInputRequirement());
assertTrue(listHdfsDefinition.isAdditionalDetails());
assertEquals("30 sec", listHdfsDefinition.getDefaultPenaltyDuration()); assertEquals("30 sec", listHdfsDefinition.getDefaultPenaltyDuration());
assertEquals("1 sec", listHdfsDefinition.getDefaultYieldDuration()); assertEquals("1 sec", listHdfsDefinition.getDefaultYieldDuration());
@ -159,6 +160,7 @@ class TestRuntimeManifest {
"org.apache.nifi.processors.hadoop.FetchHDFS"); "org.apache.nifi.processors.hadoop.FetchHDFS");
assertNotNull(fetchHdfsDefinition.isRestricted()); assertNotNull(fetchHdfsDefinition.isRestricted());
assertTrue(fetchHdfsDefinition.isRestricted()); assertTrue(fetchHdfsDefinition.isRestricted());
assertFalse(fetchHdfsDefinition.isAdditionalDetails());
final Set<Restriction> restrictions = fetchHdfsDefinition.getExplicitRestrictions(); final Set<Restriction> restrictions = fetchHdfsDefinition.getExplicitRestrictions();
assertNotNull(restrictions); assertNotNull(restrictions);
@ -171,6 +173,7 @@ class TestRuntimeManifest {
// Verify ConsumeKafka_2_6 definition which has properties with dependencies // Verify ConsumeKafka_2_6 definition which has properties with dependencies
final ProcessorDefinition consumeKafkaDefinition = getProcessorDefinition(bundles, "nifi-kafka-2-6-nar", final ProcessorDefinition consumeKafkaDefinition = getProcessorDefinition(bundles, "nifi-kafka-2-6-nar",
"org.apache.nifi.processors.kafka.pubsub.ConsumeKafka_2_6"); "org.apache.nifi.processors.kafka.pubsub.ConsumeKafka_2_6");
assertTrue(consumeKafkaDefinition.isAdditionalDetails());
final PropertyDescriptor maxUncommitProp = getPropertyDescriptor(consumeKafkaDefinition, "max-uncommit-offset-wait"); final PropertyDescriptor maxUncommitProp = getPropertyDescriptor(consumeKafkaDefinition, "max-uncommit-offset-wait");
final List<PropertyDependency> propertyDependencies = maxUncommitProp.getDependencies(); final List<PropertyDependency> propertyDependencies = maxUncommitProp.getDependencies();

View File

@ -92,7 +92,6 @@
<configuration> <configuration>
<includeArtifactIds>nifi-assembly</includeArtifactIds> <includeArtifactIds>nifi-assembly</includeArtifactIds>
<includeClassifiers>manifests</includeClassifiers> <includeClassifiers>manifests</includeClassifiers>
<includes>**/extension-manifest.xml</includes>
<excludeTransitive>true</excludeTransitive> <excludeTransitive>true</excludeTransitive>
<outputDirectory>${extension.manifest.unpack.dir}</outputDirectory> <outputDirectory>${extension.manifest.unpack.dir}</outputDirectory>
<silent>true</silent> <silent>true</silent>

View File

@ -24,6 +24,7 @@ import org.apache.nifi.extension.manifest.ExtensionManifest;
import org.apache.nifi.extension.manifest.parser.ExtensionManifestParser; import org.apache.nifi.extension.manifest.parser.ExtensionManifestParser;
import org.apache.nifi.nar.ExtensionManager; import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarClassLoadersHolder; import org.apache.nifi.nar.NarClassLoadersHolder;
import org.apache.nifi.runtime.manifest.ExtensionManifestContainer;
import org.apache.nifi.runtime.manifest.RuntimeManifestBuilder; import org.apache.nifi.runtime.manifest.RuntimeManifestBuilder;
import org.apache.nifi.runtime.manifest.impl.SchedulingDefaultsFactory; import org.apache.nifi.runtime.manifest.impl.SchedulingDefaultsFactory;
import org.apache.nifi.runtime.manifest.impl.StandardRuntimeManifestBuilder; import org.apache.nifi.runtime.manifest.impl.StandardRuntimeManifestBuilder;
@ -32,11 +33,16 @@ import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
public class StandardRuntimeManifestService implements RuntimeManifestService { public class StandardRuntimeManifestService implements RuntimeManifestService {
@ -90,12 +96,25 @@ public class StandardRuntimeManifestService implements RuntimeManifestService {
return manifestBuilder.build(); return manifestBuilder.build();
} }
private Optional<ExtensionManifest> getExtensionManifest(final Bundle bundle) { private Optional<ExtensionManifestContainer> getExtensionManifest(final Bundle bundle) {
final BundleDetails bundleDetails = bundle.getBundleDetails(); final BundleDetails bundleDetails = bundle.getBundleDetails();
try {
final ExtensionManifest extensionManifest = loadExtensionManifest(bundleDetails);
final Map<String, String> additionalDetails = loadAdditionalDetails(bundleDetails);
final ExtensionManifestContainer container = new ExtensionManifestContainer(extensionManifest, additionalDetails);
return Optional.of(container);
} catch (final IOException e) {
LOGGER.error("Unable to load extension manifest for bundle [{}]", bundleDetails.getCoordinate(), e);
return Optional.empty();
}
}
private ExtensionManifest loadExtensionManifest(final BundleDetails bundleDetails) throws IOException {
final File manifestFile = new File(bundleDetails.getWorkingDirectory(), "META-INF/docs/extension-manifest.xml"); final File manifestFile = new File(bundleDetails.getWorkingDirectory(), "META-INF/docs/extension-manifest.xml");
if (!manifestFile.exists()) { if (!manifestFile.exists()) {
LOGGER.warn("Unable to find extension manifest for [{}] at [{}]...", bundleDetails.getCoordinate(), manifestFile.getAbsolutePath()); throw new FileNotFoundException("Extension manifest files does not exist for "
return Optional.empty(); + bundleDetails.getCoordinate() + " at " + manifestFile.getAbsolutePath());
} }
try (final InputStream inputStream = new FileInputStream(manifestFile)) { try (final InputStream inputStream = new FileInputStream(manifestFile)) {
@ -105,13 +124,45 @@ public class StandardRuntimeManifestService implements RuntimeManifestService {
extensionManifest.setGroupId(bundleDetails.getCoordinate().getGroup()); extensionManifest.setGroupId(bundleDetails.getCoordinate().getGroup());
extensionManifest.setArtifactId(bundleDetails.getCoordinate().getId()); extensionManifest.setArtifactId(bundleDetails.getCoordinate().getId());
extensionManifest.setVersion(bundleDetails.getCoordinate().getVersion()); extensionManifest.setVersion(bundleDetails.getCoordinate().getVersion());
return Optional.of(extensionManifest); return extensionManifest;
} catch (final IOException e) {
LOGGER.error("Unable to load extension manifest for bundle [{}]", bundleDetails.getCoordinate(), e);
return Optional.empty();
} }
} }
private Map<String, String> loadAdditionalDetails(final BundleDetails bundleDetails) {
final Map<String, String> additionalDetailsMap = new LinkedHashMap<>();
final File additionalDetailsDir = new File(bundleDetails.getWorkingDirectory(), "META-INF/docs/additional-details");
if (!additionalDetailsDir.exists()) {
LOGGER.debug("No additional-details directory found under [{}]", bundleDetails.getWorkingDirectory().getAbsolutePath());
return additionalDetailsMap;
}
for (final File additionalDetailsTypeDir : additionalDetailsDir.listFiles()) {
if (!additionalDetailsTypeDir.isDirectory()) {
LOGGER.debug("Skipping [{}], not a directory...", additionalDetailsTypeDir.getAbsolutePath());
continue;
}
final File additionalDetailsFile = new File(additionalDetailsTypeDir, "additionalDetails.html");
if (!additionalDetailsFile.exists()) {
LOGGER.debug("No additionalDetails.html found under [{}]", additionalDetailsTypeDir.getAbsolutePath());
continue;
}
try {
final String typeName = additionalDetailsTypeDir.getName();
final String additionalDetailsContent = Files.lines(additionalDetailsFile.toPath()).collect(Collectors.joining());
LOGGER.debug("Added additionalDetails for {} from {}", typeName, additionalDetailsFile.getAbsolutePath());
additionalDetailsMap.put(typeName, additionalDetailsContent);
} catch (final IOException e) {
throw new RuntimeException("Unable to load additional details content for "
+ additionalDetailsFile.getAbsolutePath() + " due to: " + e.getMessage(), e);
}
}
return additionalDetailsMap;
}
// Visible for overriding from tests // Visible for overriding from tests
Bundle getFrameworkBundle() { Bundle getFrameworkBundle() {
return NarClassLoadersHolder.getInstance().getFrameworkBundle(); return NarClassLoadersHolder.getInstance().getFrameworkBundle();