mirror of https://github.com/apache/nifi.git
NIFI-7409: Azure managed identity support to Azure Datalake processors
NIFI-7409: review changes NIFI-7409: ordering import statements NIFI-7409: changed validateCredentialProperties logic This closes #4249. Signed-off-by: Peter Turcsanyi <turcsanyi@apache.org>
This commit is contained in:
parent
3fec4d8c27
commit
852715aadd
|
@ -56,6 +56,16 @@
|
||||||
<version>1.12.0-SNAPSHOT</version>
|
<version>1.12.0-SNAPSHOT</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.azure</groupId>
|
||||||
|
<artifactId>azure-core</artifactId>
|
||||||
|
<version>1.5.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.azure</groupId>
|
||||||
|
<artifactId>azure-identity</artifactId>
|
||||||
|
<version>1.0.6</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.microsoft.azure</groupId>
|
<groupId>com.microsoft.azure</groupId>
|
||||||
<artifactId>azure-eventhubs</artifactId>
|
<artifactId>azure-eventhubs</artifactId>
|
||||||
|
@ -75,12 +85,6 @@
|
||||||
<artifactId>azure-storage-file-datalake</artifactId>
|
<artifactId>azure-storage-file-datalake</artifactId>
|
||||||
<version>12.1.1</version>
|
<version>12.1.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- overriding jackson-core in azure-storage -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
|
||||||
<artifactId>jackson-core</artifactId>
|
|
||||||
<version>2.10.3</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.nifi</groupId>
|
<groupId>org.apache.nifi</groupId>
|
||||||
<artifactId>nifi-mock</artifactId>
|
<artifactId>nifi-mock</artifactId>
|
||||||
|
|
|
@ -16,30 +16,32 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.processors.azure;
|
package org.apache.nifi.processors.azure;
|
||||||
|
|
||||||
import org.apache.nifi.components.PropertyDescriptor;
|
|
||||||
import org.apache.nifi.components.ValidationContext;
|
|
||||||
import org.apache.nifi.components.ValidationResult;
|
|
||||||
import org.apache.nifi.components.Validator;
|
|
||||||
import org.apache.nifi.expression.ExpressionLanguageScope;
|
|
||||||
import org.apache.nifi.processor.AbstractProcessor;
|
|
||||||
import org.apache.nifi.processor.Relationship;
|
|
||||||
import org.apache.nifi.processor.util.StandardValidators;
|
|
||||||
import org.apache.nifi.context.PropertyContext;
|
|
||||||
import org.apache.nifi.flowfile.FlowFile;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import com.azure.storage.common.StorageSharedKeyCredential;
|
|
||||||
import com.azure.storage.file.datalake.DataLakeServiceClient;
|
|
||||||
import com.azure.storage.file.datalake.DataLakeServiceClientBuilder;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.azure.identity.ManagedIdentityCredential;
|
||||||
|
import com.azure.identity.ManagedIdentityCredentialBuilder;
|
||||||
|
import com.azure.storage.common.StorageSharedKeyCredential;
|
||||||
|
import com.azure.storage.file.datalake.DataLakeServiceClient;
|
||||||
|
import com.azure.storage.file.datalake.DataLakeServiceClientBuilder;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.nifi.components.PropertyDescriptor;
|
||||||
|
import org.apache.nifi.components.ValidationContext;
|
||||||
|
import org.apache.nifi.components.ValidationResult;
|
||||||
|
import org.apache.nifi.components.Validator;
|
||||||
|
import org.apache.nifi.context.PropertyContext;
|
||||||
|
import org.apache.nifi.expression.ExpressionLanguageScope;
|
||||||
|
import org.apache.nifi.flowfile.FlowFile;
|
||||||
|
import org.apache.nifi.processor.AbstractProcessor;
|
||||||
|
import org.apache.nifi.processor.Relationship;
|
||||||
|
import org.apache.nifi.processor.util.StandardValidators;
|
||||||
|
|
||||||
public abstract class AbstractAzureDataLakeStorageProcessor extends AbstractProcessor {
|
public abstract class AbstractAzureDataLakeStorageProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
|
@ -85,6 +87,13 @@ public abstract class AbstractAzureDataLakeStorageProcessor extends AbstractProc
|
||||||
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static final PropertyDescriptor USE_MANAGED_IDENTITY = new PropertyDescriptor.Builder()
|
||||||
|
.name("use-managed-identity")
|
||||||
|
.displayName("Use Azure Managed Identity")
|
||||||
|
.description("Choose whether or not to use the managed identity of Azure VM/VMSS ")
|
||||||
|
.required(false).defaultValue("false").allowableValues("true", "false")
|
||||||
|
.addValidator(StandardValidators.BOOLEAN_VALIDATOR).build();
|
||||||
|
|
||||||
public static final PropertyDescriptor FILESYSTEM = new PropertyDescriptor.Builder()
|
public static final PropertyDescriptor FILESYSTEM = new PropertyDescriptor.Builder()
|
||||||
.name("filesystem-name").displayName("Filesystem Name")
|
.name("filesystem-name").displayName("Filesystem Name")
|
||||||
.description("Name of the Azure Storage File System. It is assumed to be already existing.")
|
.description("Name of the Azure Storage File System. It is assumed to be already existing.")
|
||||||
|
@ -110,6 +119,15 @@ public abstract class AbstractAzureDataLakeStorageProcessor extends AbstractProc
|
||||||
.defaultValue("${azure.filename}")
|
.defaultValue("${azure.filename}")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static final PropertyDescriptor ENDPOINT_SUFFIX = new PropertyDescriptor.Builder()
|
||||||
|
.name("endpoint-suffix").displayName("Endpoint Suffix")
|
||||||
|
.description("Endpoint Suffix")
|
||||||
|
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
|
||||||
|
.expressionLanguageSupported(ExpressionLanguageScope.FLOWFILE_ATTRIBUTES)
|
||||||
|
.required(false)
|
||||||
|
.defaultValue("dfs.core.windows.net")
|
||||||
|
.build();
|
||||||
|
|
||||||
public static final Relationship REL_SUCCESS = new Relationship.Builder().name("success").description(
|
public static final Relationship REL_SUCCESS = new Relationship.Builder().name("success").description(
|
||||||
"Files that have been successfully written to Azure storage are transferred to this relationship")
|
"Files that have been successfully written to Azure storage are transferred to this relationship")
|
||||||
.build();
|
.build();
|
||||||
|
@ -118,9 +136,14 @@ public abstract class AbstractAzureDataLakeStorageProcessor extends AbstractProc
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private static final List<PropertyDescriptor> PROPERTIES = Collections.unmodifiableList(
|
private static final List<PropertyDescriptor> PROPERTIES = Collections.unmodifiableList(
|
||||||
Arrays.asList(AbstractAzureDataLakeStorageProcessor.ACCOUNT_NAME, AbstractAzureDataLakeStorageProcessor.ACCOUNT_KEY,
|
Arrays.asList(AbstractAzureDataLakeStorageProcessor.ACCOUNT_NAME,
|
||||||
AbstractAzureDataLakeStorageProcessor.SAS_TOKEN, AbstractAzureDataLakeStorageProcessor.FILESYSTEM,
|
AbstractAzureDataLakeStorageProcessor.ACCOUNT_KEY,
|
||||||
AbstractAzureDataLakeStorageProcessor.DIRECTORY, AbstractAzureDataLakeStorageProcessor.FILE));
|
AbstractAzureDataLakeStorageProcessor.SAS_TOKEN,
|
||||||
|
AbstractAzureDataLakeStorageProcessor.USE_MANAGED_IDENTITY,
|
||||||
|
AbstractAzureDataLakeStorageProcessor.ENDPOINT_SUFFIX,
|
||||||
|
AbstractAzureDataLakeStorageProcessor.FILESYSTEM,
|
||||||
|
AbstractAzureDataLakeStorageProcessor.DIRECTORY,
|
||||||
|
AbstractAzureDataLakeStorageProcessor.FILE));
|
||||||
|
|
||||||
private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(
|
private static final Set<Relationship> RELATIONSHIPS = Collections.unmodifiableSet(
|
||||||
new HashSet<>(Arrays.asList(
|
new HashSet<>(Arrays.asList(
|
||||||
|
@ -134,17 +157,32 @@ public abstract class AbstractAzureDataLakeStorageProcessor extends AbstractProc
|
||||||
|
|
||||||
public static Collection<ValidationResult> validateCredentialProperties(final ValidationContext validationContext) {
|
public static Collection<ValidationResult> validateCredentialProperties(final ValidationContext validationContext) {
|
||||||
final List<ValidationResult> results = new ArrayList<>();
|
final List<ValidationResult> results = new ArrayList<>();
|
||||||
final String accountName = validationContext.getProperty(ACCOUNT_NAME).getValue();
|
|
||||||
final String accountKey = validationContext.getProperty(ACCOUNT_KEY).getValue();
|
|
||||||
final String sasToken = validationContext.getProperty(SAS_TOKEN).getValue();
|
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(accountName)
|
final boolean useManagedIdentity = validationContext.getProperty(USE_MANAGED_IDENTITY).asBoolean();
|
||||||
&& ((StringUtils.isNotBlank(accountKey) && StringUtils.isNotBlank(sasToken)) || (StringUtils.isBlank(accountKey) && StringUtils.isBlank(sasToken)))) {
|
final boolean accountKeyIsSet = validationContext.getProperty(ACCOUNT_KEY).isSet();
|
||||||
results.add(new ValidationResult.Builder().subject("Azure Storage Credentials").valid(false)
|
final boolean sasTokenIsSet = validationContext.getProperty(SAS_TOKEN).isSet();
|
||||||
.explanation("either " + ACCOUNT_NAME.getDisplayName() + " with " + ACCOUNT_KEY.getDisplayName() +
|
|
||||||
" or " + ACCOUNT_NAME.getDisplayName() + " with " + SAS_TOKEN.getDisplayName() +
|
int credential_config_found = 0;
|
||||||
" must be specified, not both")
|
if(useManagedIdentity) credential_config_found++;
|
||||||
.build());
|
if(accountKeyIsSet) credential_config_found++;
|
||||||
|
if(sasTokenIsSet) credential_config_found++;
|
||||||
|
|
||||||
|
if(credential_config_found == 0){
|
||||||
|
final String msg = String.format(
|
||||||
|
"At least one of ['%s', '%s', '%s'] should be set",
|
||||||
|
ACCOUNT_KEY.getDisplayName(),
|
||||||
|
SAS_TOKEN.getDisplayName(),
|
||||||
|
USE_MANAGED_IDENTITY.getDisplayName()
|
||||||
|
);
|
||||||
|
results.add(new ValidationResult.Builder().subject("Credentials config").valid(false).explanation(msg).build());
|
||||||
|
} else if(credential_config_found > 1) {
|
||||||
|
final String msg = String.format(
|
||||||
|
"Only one of ['%s', '%s', '%s'] should be set",
|
||||||
|
ACCOUNT_KEY.getDisplayName(),
|
||||||
|
SAS_TOKEN.getDisplayName(),
|
||||||
|
USE_MANAGED_IDENTITY.getDisplayName()
|
||||||
|
);
|
||||||
|
results.add(new ValidationResult.Builder().subject("Credentials config").valid(false).explanation(msg).build());
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
@ -154,7 +192,9 @@ public abstract class AbstractAzureDataLakeStorageProcessor extends AbstractProc
|
||||||
final String accountName = context.getProperty(ACCOUNT_NAME).evaluateAttributeExpressions(attributes).getValue();
|
final String accountName = context.getProperty(ACCOUNT_NAME).evaluateAttributeExpressions(attributes).getValue();
|
||||||
final String accountKey = context.getProperty(ACCOUNT_KEY).evaluateAttributeExpressions(attributes).getValue();
|
final String accountKey = context.getProperty(ACCOUNT_KEY).evaluateAttributeExpressions(attributes).getValue();
|
||||||
final String sasToken = context.getProperty(SAS_TOKEN).evaluateAttributeExpressions(attributes).getValue();
|
final String sasToken = context.getProperty(SAS_TOKEN).evaluateAttributeExpressions(attributes).getValue();
|
||||||
final String endpoint = String.format("https://%s.dfs.core.windows.net", accountName);
|
final String endpointSuffix = context.getProperty(ENDPOINT_SUFFIX).evaluateAttributeExpressions(attributes).getValue();
|
||||||
|
final String endpoint = String.format("https://%s.%s", accountName,endpointSuffix);
|
||||||
|
final boolean useManagedIdentity = context.getProperty(USE_MANAGED_IDENTITY).asBoolean();
|
||||||
DataLakeServiceClient storageClient;
|
DataLakeServiceClient storageClient;
|
||||||
if (StringUtils.isNotBlank(accountKey)) {
|
if (StringUtils.isNotBlank(accountKey)) {
|
||||||
final StorageSharedKeyCredential credential = new StorageSharedKeyCredential(accountName,
|
final StorageSharedKeyCredential credential = new StorageSharedKeyCredential(accountName,
|
||||||
|
@ -164,6 +204,13 @@ public abstract class AbstractAzureDataLakeStorageProcessor extends AbstractProc
|
||||||
} else if (StringUtils.isNotBlank(sasToken)) {
|
} else if (StringUtils.isNotBlank(sasToken)) {
|
||||||
storageClient = new DataLakeServiceClientBuilder().endpoint(endpoint).sasToken(sasToken)
|
storageClient = new DataLakeServiceClientBuilder().endpoint(endpoint).sasToken(sasToken)
|
||||||
.buildClient();
|
.buildClient();
|
||||||
|
} else if(useManagedIdentity){
|
||||||
|
final ManagedIdentityCredential misCrendential = new ManagedIdentityCredentialBuilder()
|
||||||
|
.build();
|
||||||
|
storageClient = new DataLakeServiceClientBuilder()
|
||||||
|
.endpoint(endpoint)
|
||||||
|
.credential(misCrendential)
|
||||||
|
.buildClient();
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException(String.format("Either '%s' or '%s' must be defined.",
|
throw new IllegalArgumentException(String.format("Either '%s' or '%s' must be defined.",
|
||||||
ACCOUNT_KEY.getDisplayName(), SAS_TOKEN.getDisplayName()));
|
ACCOUNT_KEY.getDisplayName(), SAS_TOKEN.getDisplayName()));
|
||||||
|
|
|
@ -16,12 +16,13 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.services.azure.storage;
|
package org.apache.nifi.services.azure.storage;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.nifi.annotation.behavior.DynamicProperty;
|
import org.apache.nifi.annotation.behavior.DynamicProperty;
|
||||||
import org.apache.nifi.annotation.documentation.CapabilityDescription;
|
import org.apache.nifi.annotation.documentation.CapabilityDescription;
|
||||||
import org.apache.nifi.annotation.documentation.Tags;
|
import org.apache.nifi.annotation.documentation.Tags;
|
||||||
import org.apache.nifi.expression.ExpressionLanguageScope;
|
import org.apache.nifi.expression.ExpressionLanguageScope;
|
||||||
import org.apache.nifi.service.lookup.AbstractSingleAttributeBasedControllerServiceLookup;
|
import org.apache.nifi.service.lookup.AbstractSingleAttributeBasedControllerServiceLookup;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Tags({ "azure", "microsoft", "cloud", "storage", "blob", "queue", "credentials" })
|
@Tags({ "azure", "microsoft", "cloud", "storage", "blob", "queue", "credentials" })
|
||||||
@CapabilityDescription("Provides an AzureStorageCredentialsService that can be used to dynamically select another AzureStorageCredentialsService. " +
|
@CapabilityDescription("Provides an AzureStorageCredentialsService that can be used to dynamically select another AzureStorageCredentialsService. " +
|
||||||
|
|
|
@ -16,17 +16,18 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.processors.azure.storage;
|
package org.apache.nifi.processors.azure.storage;
|
||||||
|
|
||||||
import org.apache.nifi.util.TestRunner;
|
|
||||||
import org.apache.nifi.util.TestRunners;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.ACCOUNT_KEY;
|
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.ACCOUNT_KEY;
|
||||||
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.ACCOUNT_NAME;
|
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.ACCOUNT_NAME;
|
||||||
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.DIRECTORY;
|
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.DIRECTORY;
|
||||||
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.FILE;
|
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.FILE;
|
||||||
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.FILESYSTEM;
|
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.FILESYSTEM;
|
||||||
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.SAS_TOKEN;
|
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.SAS_TOKEN;
|
||||||
|
import static org.apache.nifi.processors.azure.AbstractAzureDataLakeStorageProcessor.USE_MANAGED_IDENTITY;
|
||||||
|
|
||||||
|
import org.apache.nifi.util.TestRunner;
|
||||||
|
import org.apache.nifi.util.TestRunners;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
public class TestAbstractAzureDataLakeStorage {
|
public class TestAbstractAzureDataLakeStorage {
|
||||||
|
|
||||||
|
@ -57,6 +58,14 @@ public class TestAbstractAzureDataLakeStorage {
|
||||||
runner.assertValid();
|
runner.assertValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidWhenAccountNameAndUseManagedIdentity() {
|
||||||
|
runner.removeProperty(ACCOUNT_KEY);
|
||||||
|
runner.setProperty(USE_MANAGED_IDENTITY, "true");
|
||||||
|
|
||||||
|
runner.assertValid();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNotValidWhenNoAccountNameSpecified() {
|
public void testNotValidWhenNoAccountNameSpecified() {
|
||||||
runner.removeProperty(ACCOUNT_NAME);
|
runner.removeProperty(ACCOUNT_NAME);
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<azure-storage.version>8.4.0</azure-storage.version>
|
<azure-storage.version>8.4.0</azure-storage.version>
|
||||||
|
<jackson.version>2.10.3</jackson.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
@ -50,6 +51,47 @@
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- dependency convergency resolution -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-core</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-annotations</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
|
<artifactId>jackson-dataformat-xml</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.module</groupId>
|
||||||
|
<artifactId>jackson-module-jaxb-annotations</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.9</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-text</artifactId>
|
||||||
|
<version>1.8</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
</project>
|
</project>
|
||||||
|
|
Loading…
Reference in New Issue