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
82 changes: 82 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ The following table summarizes what each rule reports and which identifiers you
| `noSelfLoop` | Component name/type and UUID | Processor/Funnel UUID at both ends of the self-loop |
| `enforcePrioritizer` | Connection description and UUID | Connection UUID (`VersionedConnection#getIdentifier`) |
| `backpressureThreshold` | Connection description and UUID | Connection UUID (`VersionedConnection#getIdentifier`) |
| `processorNaming` | Processor name, type, and UUID | Processor UUID (`VersionedProcessor#getIdentifier`) |
| `controllerServiceNaming` | Controller Service name, type, and UUID | Controller Service UUID (`VersionedControllerService#getIdentifier`) |
| `parameterContextNaming` | Parameter Context name | _Not applicable_ |
| `parameterProviderNaming` | Parameter Provider name, type, and UUID | _Not applicable_ |

Available rules:
- `concurrentTasks` to check the number of concurrent tasks and define an upper limit (parameter: `limit`, default value is 2)
Expand All @@ -173,6 +177,84 @@ Available rules:
- `noSelfLoop` to check if there are self-loop connections in the flow
- `enforcePrioritizer` to check if all connections in the flow are set with the configured list of prioritizers (parameter: `prioritizers`, comma-separated list of expected prioritizers, example: `org.apache.nifi.prioritizer.FirstInFirstOutPrioritizer`)
- `backpressureThreshold` to ensure each connection keeps both data size and object count backpressure thresholds greater than zero
- `processorNaming` to validate processor names against regex patterns based on processor type (parameters: `patterns` map of fully qualified type to regex, optional `defaultPattern` for types not in the map). This rule produces no violations when no patterns are configured.
- `controllerServiceNaming` to validate controller service names against regex patterns based on service type (parameters: `patterns` map of fully qualified type to regex, optional `defaultPattern` for types not in the map). This rule produces no violations when no patterns are configured.
- `parameterContextNaming` to validate parameter context names against a regex pattern (parameters: `defaultPattern` regex, optional `exclude` list of exact names to skip). This rule produces no violations when no pattern is configured.
- `parameterProviderNaming` to validate parameter provider names against regex patterns based on provider type (parameters: `patterns` map of fully qualified type to regex, optional `defaultPattern` for types not in the map). This rule produces no violations when no patterns are configured.

### Naming convention rules

The naming rules (`processorNaming`, `controllerServiceNaming`, `parameterContextNaming`, `parameterProviderNaming`) allow you to enforce custom naming conventions on NiFi components. They are no-ops without configuration, so they are safe to include without side effects.

#### Processor naming

Validate processor names by type. Use `patterns` to map fully qualified processor types to regex patterns, and `defaultPattern` as a fallback for types not explicitly listed:

```yaml
include:
- processorNaming
rules:
processorNaming:
parameters:
patterns:
org.apache.nifi.processors.snowflake.PutSnowflakeInternalStage: ".*_ISTG$"
org.apache.nifi.processors.standard.ExecuteSQLRecord: ".*_(SEL|COPY|UPD_SEL|UPD_INS|EXT)$|^SP_.*"
org.apache.nifi.processors.standard.GenerateFlowFile: "^GENERATE_FLOW_FILE$"
org.apache.nifi.processors.standard.UpdateAttribute: "^UPDATE_ATTRIBUTE$"
org.apache.nifi.processors.standard.InvokeHTTP: ".*_EXT$"
defaultPattern: "^[A-Z][A-Z0-9_]+$"
componentExclusions:
"ProductionFlow.*":
- "some-processor-uuid-to-ignore"
```

#### Controller service naming

Validate controller service names by type. Works the same as processor naming:

```yaml
include:
- controllerServiceNaming
rules:
controllerServiceNaming:
parameters:
patterns:
org.apache.nifi.dbcp.DBCPConnectionPool: "^acme_(dev|preprod|prod)_[a-z0-9_]+$"
defaultPattern: "^[a-z][a-z0-9_]+$"
```

#### Parameter context naming

Validate parameter context names against a single pattern. Use `exclude` to skip specific context names:

```yaml
include:
- parameterContextNaming
rules:
parameterContextNaming:
parameters:
defaultPattern: "^acme_(dev|preprod|prod)_[a-z0-9_]+_(secrets|config)$"
exclude:
- "common_parameter_context"
- "common_sftp_serverkey"
- "CommonDatabaseDriversReference"
```

#### Parameter provider naming

Validate parameter provider (management controller service) names by type:

```yaml
include:
- parameterProviderNaming
rules:
parameterProviderNaming:
parameters:
patterns:
org.apache.nifi.parameter.aws.AwsSecretsManagerParameterProvider: "^acme_(dev|preprod|prod)_[a-z0-9_]+_secrets$"
```

All naming rules support the standard `overrides` mechanism to apply different patterns per flow name, and `componentExclusions` to silence violations for specific component UUIDs (where applicable).

## Example

Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2025 Snowflake Inc.
# Copyright 2026 Snowflake Inc.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Snowflake Inc.
* Copyright 2026 Snowflake Inc.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Snowflake Inc.
* Copyright 2026 Snowflake Inc.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Snowflake Inc.
* Copyright 2026 Snowflake Inc.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Snowflake Inc.
* Copyright 2026 Snowflake Inc.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Snowflake Inc.
* Copyright 2026 Snowflake Inc.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -19,12 +19,16 @@
import com.snowflake.openflow.checkstyle.CheckstyleRulesConfig.RuleConfig;
import com.snowflake.openflow.checkstyle.rules.BackpressureThresholdRule;
import com.snowflake.openflow.checkstyle.rules.ConcurrentTasksRule;
import com.snowflake.openflow.checkstyle.rules.DefaultParameterRule;
import com.snowflake.openflow.checkstyle.rules.EmptyParameterRule;
import com.snowflake.openflow.checkstyle.rules.EnforcePrioritizer;
import com.snowflake.openflow.checkstyle.rules.NoSelfLoopRule;
import com.snowflake.openflow.checkstyle.rules.SnapshotMetadataRule;
import com.snowflake.openflow.checkstyle.rules.UnusedParameterRule;
import com.snowflake.openflow.checkstyle.rules.naming.ControllerServiceNamingRule;
import com.snowflake.openflow.checkstyle.rules.naming.ParameterContextNamingRule;
import com.snowflake.openflow.checkstyle.rules.naming.ParameterProviderNamingRule;
import com.snowflake.openflow.checkstyle.rules.naming.ProcessorNamingRule;
import com.snowflake.openflow.checkstyle.rules.parameter.DefaultParameterRule;
import com.snowflake.openflow.checkstyle.rules.parameter.EmptyParameterRule;
import com.snowflake.openflow.checkstyle.rules.parameter.UnusedParameterRule;

import java.util.List;

Expand All @@ -37,7 +41,11 @@ public enum DefaultCheckstyleRules {
UNUSED_PARAMETER("unusedParameter", new UnusedParameterRule()),
NO_SELF_LOOP("noSelfLoop", new NoSelfLoopRule()),
ENFORCE_PRIORITIZER("enforcePrioritizer", new EnforcePrioritizer()),
BACKPRESSURE_THRESHOLD("backpressureThreshold", new BackpressureThresholdRule());
BACKPRESSURE_THRESHOLD("backpressureThreshold", new BackpressureThresholdRule()),
PROCESSOR_NAMING("processorNaming", new ProcessorNamingRule()),
CONTROLLER_SERVICE_NAMING("controllerServiceNaming", new ControllerServiceNamingRule()),
PARAMETER_CONTEXT_NAMING("parameterContextNaming", new ParameterContextNamingRule()),
PARAMETER_PROVIDER_NAMING("parameterProviderNaming", new ParameterProviderNamingRule());

private final String id;
private final CheckstyleRule implementation;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Snowflake Inc.
* Copyright 2026 Snowflake Inc.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Snowflake Inc.
* Copyright 2026 Snowflake Inc.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Snowflake Inc.
* Copyright 2026 Snowflake Inc.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Snowflake Inc.
* Copyright 2026 Snowflake Inc.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2025 Snowflake Inc.
* Copyright 2026 Snowflake Inc.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2026 Snowflake Inc.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed 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 com.snowflake.openflow.checkstyle.rules.naming;

import com.snowflake.openflow.checkstyle.CheckstyleRule;
import com.snowflake.openflow.checkstyle.CheckstyleRulesConfig.RuleConfig;

import java.util.Map;

public abstract class AbstractNamingRule implements CheckstyleRule {

private static final String PATTERNS_KEY = "patterns";
private static final String DEFAULT_PATTERN_KEY = "defaultPattern";

protected Map<String, Object> getPatterns(final RuleConfig ruleConfig, final String flowName) {
Map<String, Object> patterns = null;

if (ruleConfig != null && ruleConfig.parameters() != null && ruleConfig.parameters().get(PATTERNS_KEY) != null) {
patterns = (Map<String, Object>) ruleConfig.parameters().get(PATTERNS_KEY);
}

if (ruleConfig != null && ruleConfig.overrides() != null) {
for (final Map.Entry<String, Map<String, Object>> entry : ruleConfig.overrides().entrySet()) {
if (flowName != null && flowName.matches(entry.getKey())) {
final Object val = entry.getValue().get(PATTERNS_KEY);
if (val != null) {
patterns = (Map<String, Object>) val;
}
}
}
}

return patterns;
}

protected String getDefaultPattern(final RuleConfig ruleConfig, final String flowName) {
String defaultPattern = null;

if (ruleConfig != null && ruleConfig.parameters() != null && ruleConfig.parameters().get(DEFAULT_PATTERN_KEY) != null) {
defaultPattern = ruleConfig.parameters().get(DEFAULT_PATTERN_KEY).toString();
}

if (ruleConfig != null && ruleConfig.overrides() != null) {
for (final Map.Entry<String, Map<String, Object>> entry : ruleConfig.overrides().entrySet()) {
if (flowName != null && flowName.matches(entry.getKey())) {
final Object val = entry.getValue().get(DEFAULT_PATTERN_KEY);
if (val != null) {
defaultPattern = val.toString();
}
}
}
}

return defaultPattern;
}

protected String resolvePattern(final String componentType, final Map<String, Object> patternsMap, final String defaultPattern) {
if (patternsMap != null && patternsMap.containsKey(componentType)) {
return patternsMap.get(componentType).toString();
}
return defaultPattern;
}

protected String shortType(final String fullyQualifiedType) {
if (fullyQualifiedType == null || !fullyQualifiedType.contains(".")) {
return fullyQualifiedType;
}
return fullyQualifiedType.substring(fullyQualifiedType.lastIndexOf('.') + 1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2026 Snowflake Inc.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed 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 com.snowflake.openflow.checkstyle.rules.naming;

import com.snowflake.openflow.checkstyle.CheckstyleRulesConfig.RuleConfig;
import org.apache.nifi.flow.VersionedControllerService;
import org.apache.nifi.flow.VersionedProcessGroup;
import org.apache.nifi.registry.flow.FlowSnapshotContainer;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class ControllerServiceNamingRule extends AbstractNamingRule {

@Override
public List<String> check(final FlowSnapshotContainer container, final String flowName, final RuleConfig config) {
final VersionedProcessGroup rootProcessGroup = container.getFlowSnapshot().getFlowContents();
return checkControllerServiceNaming(rootProcessGroup, config, flowName);
}

private List<String> checkControllerServiceNaming(final VersionedProcessGroup processGroup, final RuleConfig ruleConfig, final String flowName) {
final List<String> violations = new ArrayList<>();

for (final VersionedProcessGroup childGroup : processGroup.getProcessGroups()) {
violations.addAll(checkControllerServiceNaming(childGroup, ruleConfig, flowName));
}

final Map<String, Object> patternsMap = getPatterns(ruleConfig, flowName);
final String defaultPattern = getDefaultPattern(ruleConfig, flowName);

if (patternsMap == null && defaultPattern == null) {
return violations;
}

for (final VersionedControllerService service : processGroup.getControllerServices()) {
if (ruleConfig != null && ruleConfig.isComponentExcluded(flowName, service.getIdentifier())) {
continue;
}

final String pattern = resolvePattern(service.getType(), patternsMap, defaultPattern);

if (pattern != null && !service.getName().matches(pattern)) {
violations.add("Controller Service of type `%s` named `%s` does not match the expected naming pattern `%s` (id: `%s`)".formatted(
shortType(service.getType()), service.getName(), pattern, service.getIdentifier()));
}
}

return violations;
}
}
Loading
Loading