diff --git a/README.md b/README.md index 72bbdaa..0f6cee4 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 diff --git a/action.yml b/action.yml index eee1a57..9edebae 100644 --- a/action.yml +++ b/action.yml @@ -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"); diff --git a/flow-diff/src/main/java/com/snowflake/openflow/FlowCheckstyle.java b/flow-diff/src/main/java/com/snowflake/openflow/FlowCheckstyle.java index 8383f91..34b5261 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/FlowCheckstyle.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/FlowCheckstyle.java @@ -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"); diff --git a/flow-diff/src/main/java/com/snowflake/openflow/FlowDiff.java b/flow-diff/src/main/java/com/snowflake/openflow/FlowDiff.java index 8b357c0..781a306 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/FlowDiff.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/FlowDiff.java @@ -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"); diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/CheckstyleRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/CheckstyleRule.java index 7cb7ada..5899733 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/CheckstyleRule.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/CheckstyleRule.java @@ -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"); diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/CheckstyleRulesConfig.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/CheckstyleRulesConfig.java index 99a097e..4577e75 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/CheckstyleRulesConfig.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/CheckstyleRulesConfig.java @@ -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"); diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/DefaultCheckstyleRules.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/DefaultCheckstyleRules.java index a786626..a9a3150 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/DefaultCheckstyleRules.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/DefaultCheckstyleRules.java @@ -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"); @@ -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; @@ -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; diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/BackpressureThresholdRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/BackpressureThresholdRule.java index a31e5e7..3b8cf49 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/BackpressureThresholdRule.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/BackpressureThresholdRule.java @@ -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"); diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/ConcurrentTasksRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/ConcurrentTasksRule.java index ac9479b..eb90c93 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/ConcurrentTasksRule.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/ConcurrentTasksRule.java @@ -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"); diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/EnforcePrioritizer.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/EnforcePrioritizer.java index a4c7a69..34436a9 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/EnforcePrioritizer.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/EnforcePrioritizer.java @@ -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"); diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/NoSelfLoopRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/NoSelfLoopRule.java index 5c7814b..83e671e 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/NoSelfLoopRule.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/NoSelfLoopRule.java @@ -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"); diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/SnapshotMetadataRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/SnapshotMetadataRule.java index 36c6e57..b830e86 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/SnapshotMetadataRule.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/SnapshotMetadataRule.java @@ -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"); diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/AbstractNamingRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/AbstractNamingRule.java new file mode 100644 index 0000000..220ca27 --- /dev/null +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/AbstractNamingRule.java @@ -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 getPatterns(final RuleConfig ruleConfig, final String flowName) { + Map patterns = null; + + if (ruleConfig != null && ruleConfig.parameters() != null && ruleConfig.parameters().get(PATTERNS_KEY) != null) { + patterns = (Map) ruleConfig.parameters().get(PATTERNS_KEY); + } + + if (ruleConfig != null && ruleConfig.overrides() != null) { + for (final Map.Entry> entry : ruleConfig.overrides().entrySet()) { + if (flowName != null && flowName.matches(entry.getKey())) { + final Object val = entry.getValue().get(PATTERNS_KEY); + if (val != null) { + patterns = (Map) 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> 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 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); + } +} diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/ControllerServiceNamingRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/ControllerServiceNamingRule.java new file mode 100644 index 0000000..a252b9f --- /dev/null +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/ControllerServiceNamingRule.java @@ -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 check(final FlowSnapshotContainer container, final String flowName, final RuleConfig config) { + final VersionedProcessGroup rootProcessGroup = container.getFlowSnapshot().getFlowContents(); + return checkControllerServiceNaming(rootProcessGroup, config, flowName); + } + + private List checkControllerServiceNaming(final VersionedProcessGroup processGroup, final RuleConfig ruleConfig, final String flowName) { + final List violations = new ArrayList<>(); + + for (final VersionedProcessGroup childGroup : processGroup.getProcessGroups()) { + violations.addAll(checkControllerServiceNaming(childGroup, ruleConfig, flowName)); + } + + final Map 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; + } +} diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/ParameterContextNamingRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/ParameterContextNamingRule.java new file mode 100644 index 0000000..3184a59 --- /dev/null +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/ParameterContextNamingRule.java @@ -0,0 +1,81 @@ +/* + * 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.VersionedParameterContext; +import org.apache.nifi.registry.flow.FlowSnapshotContainer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class ParameterContextNamingRule extends AbstractNamingRule { + + private static final String EXCLUDE_KEY = "exclude"; + + @Override + public List check(final FlowSnapshotContainer container, final String flowName, final RuleConfig config) { + final String defaultPattern = getDefaultPattern(config, flowName); + if (defaultPattern == null) { + return List.of(); + } + + final List excludeNames = getExcludeNames(config, flowName); + final Map parameterContexts = container.getFlowSnapshot().getParameterContexts(); + if (parameterContexts == null || parameterContexts.isEmpty()) { + return List.of(); + } + + final List violations = new ArrayList<>(); + + for (final VersionedParameterContext context : parameterContexts.values()) { + final String contextName = context.getName(); + if (excludeNames.contains(contextName)) { + continue; + } + + if (!contextName.matches(defaultPattern)) { + violations.add("Parameter Context named `%s` does not match the expected naming pattern `%s`".formatted(contextName, defaultPattern)); + } + } + + return violations; + } + + private List getExcludeNames(final RuleConfig ruleConfig, final String flowName) { + List excludeNames = Collections.emptyList(); + + if (ruleConfig != null && ruleConfig.parameters() != null && ruleConfig.parameters().get(EXCLUDE_KEY) != null) { + excludeNames = new ArrayList<>((List) ruleConfig.parameters().get(EXCLUDE_KEY)); + } + + if (ruleConfig != null && ruleConfig.overrides() != null) { + for (final Map.Entry> entry : ruleConfig.overrides().entrySet()) { + if (flowName != null && flowName.matches(entry.getKey())) { + final Object val = entry.getValue().get(EXCLUDE_KEY); + if (val != null) { + excludeNames = new ArrayList<>((List) val); + } + } + } + } + + return excludeNames; + } +} diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/ParameterProviderNamingRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/ParameterProviderNamingRule.java new file mode 100644 index 0000000..5652651 --- /dev/null +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/ParameterProviderNamingRule.java @@ -0,0 +1,56 @@ +/* + * 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.ParameterProviderReference; +import org.apache.nifi.registry.flow.FlowSnapshotContainer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ParameterProviderNamingRule extends AbstractNamingRule { + + @Override + public List check(final FlowSnapshotContainer container, final String flowName, final RuleConfig config) { + final Map parameterProviders = container.getFlowSnapshot().getParameterProviders(); + if (parameterProviders == null || parameterProviders.isEmpty()) { + return List.of(); + } + + final Map patternsMap = getPatterns(config, flowName); + final String defaultPattern = getDefaultPattern(config, flowName); + + if (patternsMap == null && defaultPattern == null) { + return List.of(); + } + + final List violations = new ArrayList<>(); + + for (final ParameterProviderReference provider : parameterProviders.values()) { + final String pattern = resolvePattern(provider.getType(), patternsMap, defaultPattern); + + if (pattern != null && !provider.getName().matches(pattern)) { + violations.add("Parameter Provider of type `%s` named `%s` does not match the expected naming pattern `%s` (id: `%s`)".formatted( + shortType(provider.getType()), provider.getName(), pattern, provider.getIdentifier())); + } + } + + return violations; + } +} diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/ProcessorNamingRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/ProcessorNamingRule.java new file mode 100644 index 0000000..5ea23e1 --- /dev/null +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/naming/ProcessorNamingRule.java @@ -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.VersionedProcessGroup; +import org.apache.nifi.flow.VersionedProcessor; +import org.apache.nifi.registry.flow.FlowSnapshotContainer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ProcessorNamingRule extends AbstractNamingRule { + + @Override + public List check(final FlowSnapshotContainer container, final String flowName, final RuleConfig config) { + final VersionedProcessGroup rootProcessGroup = container.getFlowSnapshot().getFlowContents(); + return checkProcessorNaming(rootProcessGroup, config, flowName); + } + + private List checkProcessorNaming(final VersionedProcessGroup processGroup, final RuleConfig ruleConfig, final String flowName) { + final List violations = new ArrayList<>(); + + for (final VersionedProcessGroup childGroup : processGroup.getProcessGroups()) { + violations.addAll(checkProcessorNaming(childGroup, ruleConfig, flowName)); + } + + final Map patternsMap = getPatterns(ruleConfig, flowName); + final String defaultPattern = getDefaultPattern(ruleConfig, flowName); + + if (patternsMap == null && defaultPattern == null) { + return violations; + } + + for (final VersionedProcessor processor : processGroup.getProcessors()) { + if (ruleConfig != null && ruleConfig.isComponentExcluded(flowName, processor.getIdentifier())) { + continue; + } + + final String pattern = resolvePattern(processor.getType(), patternsMap, defaultPattern); + + if (pattern != null && !processor.getName().matches(pattern)) { + violations.add("Processor of type `%s` named `%s` does not match the expected naming pattern `%s` (id: `%s`)".formatted( + shortType(processor.getType()), processor.getName(), pattern, processor.getIdentifier())); + } + } + + return violations; + } +} diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/DefaultParameterRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/parameter/DefaultParameterRule.java similarity index 97% rename from flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/DefaultParameterRule.java rename to flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/parameter/DefaultParameterRule.java index 44b7bfe..2178ee9 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/DefaultParameterRule.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/parameter/DefaultParameterRule.java @@ -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"); @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.snowflake.openflow.checkstyle.rules; +package com.snowflake.openflow.checkstyle.rules.parameter; import com.snowflake.openflow.checkstyle.CheckstyleRule; import com.snowflake.openflow.checkstyle.CheckstyleRulesConfig.RuleConfig; diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/EmptyParameterRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/parameter/EmptyParameterRule.java similarity index 94% rename from flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/EmptyParameterRule.java rename to flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/parameter/EmptyParameterRule.java index b9f27fe..9e618e6 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/EmptyParameterRule.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/parameter/EmptyParameterRule.java @@ -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"); @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.snowflake.openflow.checkstyle.rules; +package com.snowflake.openflow.checkstyle.rules.parameter; import com.snowflake.openflow.checkstyle.CheckstyleRule; import com.snowflake.openflow.checkstyle.CheckstyleRulesConfig.RuleConfig; diff --git a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/UnusedParameterRule.java b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/parameter/UnusedParameterRule.java similarity index 98% rename from flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/UnusedParameterRule.java rename to flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/parameter/UnusedParameterRule.java index a42b004..3bd6d7f 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/UnusedParameterRule.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/checkstyle/rules/parameter/UnusedParameterRule.java @@ -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"); @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.snowflake.openflow.checkstyle.rules; +package com.snowflake.openflow.checkstyle.rules.parameter; import com.snowflake.openflow.checkstyle.CheckstyleRule; import com.snowflake.openflow.checkstyle.CheckstyleRulesConfig.RuleConfig; diff --git a/flow-diff/src/main/java/com/snowflake/openflow/github/GitHubClient.java b/flow-diff/src/main/java/com/snowflake/openflow/github/GitHubClient.java index 4d585b9..5938b97 100644 --- a/flow-diff/src/main/java/com/snowflake/openflow/github/GitHubClient.java +++ b/flow-diff/src/main/java/com/snowflake/openflow/github/GitHubClient.java @@ -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"); diff --git a/flow-diff/src/test/java/com/snowflake/openflow/FlowCheckstyleTest.java b/flow-diff/src/test/java/com/snowflake/openflow/FlowCheckstyleTest.java index ff68aea..ad2504e 100644 --- a/flow-diff/src/test/java/com/snowflake/openflow/FlowCheckstyleTest.java +++ b/flow-diff/src/test/java/com/snowflake/openflow/FlowCheckstyleTest.java @@ -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"); @@ -201,4 +201,130 @@ void testBackpressureThresholdNoViolationsWhenPositive() throws IOException { assertEquals(0, violations.size()); } + + @Test + void testProcessorNamingViolations() throws IOException { + final FlowSnapshotContainer container = FlowDiff.getFlowContainer("src/test/resources/flow_v7_naming.json", jsonFactory); + final CheckstyleRulesConfig config = CheckstyleRulesConfig.fromFile("src/test/resources/checkstyle_processor_naming.yaml"); + final List violations = FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config); + + assertEquals(1, violations.size()); + assertTrue(violations.stream().anyMatch(v -> v.contains("InvokeHTTP") && v.contains("does not match") && v.contains("proc-invoke-http-001"))); + } + + @Test + void testProcessorNamingNoViolationsWhenCompliant() throws IOException { + final FlowSnapshotContainer container = FlowDiff.getFlowContainer("src/test/resources/flow_v7_naming.json", jsonFactory); + final CheckstyleRulesConfig config = CheckstyleRulesConfig.fromFile("src/test/resources/checkstyle_processor_naming.yaml"); + final List violations = FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config); + + assertTrue(violations.stream().noneMatch(v -> v.contains("GENERATE_FLOW_FILE"))); + assertTrue(violations.stream().noneMatch(v -> v.contains("UPDATE_ATTRIBUTE"))); + assertTrue(violations.stream().noneMatch(v -> v.contains("CUSTOMER_SEL"))); + assertTrue(violations.stream().noneMatch(v -> v.contains("CUSTOMER_ISTG"))); + } + + @Test + void testProcessorNamingDefaultPattern() throws IOException { + final FlowSnapshotContainer container = FlowDiff.getFlowContainer("src/test/resources/flow_v7_naming.json", jsonFactory); + final CheckstyleRulesConfig config = CheckstyleRulesConfig.fromFile("src/test/resources/checkstyle_processor_naming_default.yaml"); + final List violations = FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config); + + assertEquals(1, violations.size()); + assertTrue(violations.stream().anyMatch(v -> v.contains("InvokeHTTP") && v.contains("does not match"))); + } + + @Test + void testProcessorNamingComponentExclusion() throws IOException { + final FlowSnapshotContainer container = FlowDiff.getFlowContainer("src/test/resources/flow_v7_naming.json", jsonFactory); + final CheckstyleRulesConfig config = CheckstyleRulesConfig.fromFile("src/test/resources/checkstyle_naming_component_exclusions.yaml"); + final List violations = FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config); + + assertEquals(0, violations.size()); + } + + @Test + void testProcessorNamingOverride() throws IOException { + final FlowSnapshotContainer container = FlowDiff.getFlowContainer("src/test/resources/flow_v7_naming.json", jsonFactory); + final CheckstyleRulesConfig config = CheckstyleRulesConfig.fromFile("src/test/resources/checkstyle_naming_overrides.yaml"); + final List violations = FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config); + + assertEquals(0, violations.size()); + } + + @Test + void testControllerServiceNamingViolations() throws IOException { + final FlowSnapshotContainer container = FlowDiff.getFlowContainer("src/test/resources/flow_v7_naming.json", jsonFactory); + final CheckstyleRulesConfig config = CheckstyleRulesConfig.fromFile("src/test/resources/checkstyle_controller_service_naming.yaml"); + final List violations = FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config); + + assertEquals(1, violations.size()); + assertTrue(violations.stream().anyMatch(v -> v.contains("Snowconnection_DEV") && v.contains("does not match") && v.contains("cs-dbcp-invalid-001"))); + } + + @Test + void testControllerServiceNamingValidNames() throws IOException { + final FlowSnapshotContainer container = FlowDiff.getFlowContainer("src/test/resources/flow_v7_naming.json", jsonFactory); + final CheckstyleRulesConfig config = CheckstyleRulesConfig.fromFile("src/test/resources/checkstyle_controller_service_naming.yaml"); + final List violations = FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config); + + assertTrue(violations.stream().noneMatch(v -> v.contains("acme_prod_mssql_mds"))); + assertTrue(violations.stream().noneMatch(v -> v.contains("csv_record_writer"))); + } + + @Test + void testParameterContextNamingViolations() throws IOException { + final FlowSnapshotContainer container = FlowDiff.getFlowContainer("src/test/resources/flow_v7_naming.json", jsonFactory); + final CheckstyleRulesConfig config = CheckstyleRulesConfig.fromFile("src/test/resources/checkstyle_parameter_context_naming.yaml"); + final List violations = FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config); + + assertEquals(1, violations.size()); + assertTrue(violations.stream().anyMatch(v -> v.contains("Test Parameter Context") && v.contains("does not match"))); + } + + @Test + void testParameterContextNamingExclude() throws IOException { + final FlowSnapshotContainer container = FlowDiff.getFlowContainer("src/test/resources/flow_v7_naming.json", jsonFactory); + final CheckstyleRulesConfig config = CheckstyleRulesConfig.fromFile("src/test/resources/checkstyle_parameter_context_naming.yaml"); + final List violations = FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config); + + assertTrue(violations.stream().noneMatch(v -> v.contains("common_parameter_context"))); + assertTrue(violations.stream().noneMatch(v -> v.contains("acme_prod_postgres_rbs_inventorydb_secrets"))); + } + + @Test + void testParameterProviderNamingViolations() throws IOException { + final FlowSnapshotContainer container = FlowDiff.getFlowContainer("src/test/resources/flow_v7_naming.json", jsonFactory); + final CheckstyleRulesConfig config = CheckstyleRulesConfig.fromFile("src/test/resources/checkstyle_parameter_provider_naming.yaml"); + final List violations = FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config); + + assertEquals(1, violations.size()); + assertTrue(violations.stream().anyMatch(v -> v.contains("Acme_Prod_Mssql_Secrets") && v.contains("does not match") && v.contains("pp-invalid-001"))); + } + + @Test + void testParameterProviderNamingValidNames() throws IOException { + final FlowSnapshotContainer container = FlowDiff.getFlowContainer("src/test/resources/flow_v7_naming.json", jsonFactory); + final CheckstyleRulesConfig config = CheckstyleRulesConfig.fromFile("src/test/resources/checkstyle_parameter_provider_naming.yaml"); + final List violations = FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config); + + assertTrue(violations.stream().noneMatch(v -> v.contains("acme_prod_mssql_landmark_secrets"))); + } + + @Test + void testNamingRulesNoViolationsWithoutConfig() throws IOException { + final FlowSnapshotContainer container = FlowDiff.getFlowContainer("src/test/resources/flow_v7_naming.json", jsonFactory); + + CheckstyleRulesConfig config = new CheckstyleRulesConfig(List.of("processorNaming"), null, null); + assertEquals(0, FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config).size()); + + config = new CheckstyleRulesConfig(List.of("controllerServiceNaming"), null, null); + assertEquals(0, FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config).size()); + + config = new CheckstyleRulesConfig(List.of("parameterContextNaming"), null, null); + assertEquals(0, FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config).size()); + + config = new CheckstyleRulesConfig(List.of("parameterProviderNaming"), null, null); + assertEquals(0, FlowCheckstyle.getCheckstyleViolations(container, container.getFlowSnapshot().getFlow().getName(), config).size()); + } } diff --git a/flow-diff/src/test/java/com/snowflake/openflow/FlowDiffTest.java b/flow-diff/src/test/java/com/snowflake/openflow/FlowDiffTest.java index bc2a288..4e2fe89 100644 --- a/flow-diff/src/test/java/com/snowflake/openflow/FlowDiffTest.java +++ b/flow-diff/src/test/java/com/snowflake/openflow/FlowDiffTest.java @@ -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"); diff --git a/flow-diff/src/test/resources/checkstyle_component_exclusions_backpressure.yaml b/flow-diff/src/test/resources/checkstyle_component_exclusions_backpressure.yaml index 6efe480..987d795 100644 --- a/flow-diff/src/test/resources/checkstyle_component_exclusions_backpressure.yaml +++ b/flow-diff/src/test/resources/checkstyle_component_exclusions_backpressure.yaml @@ -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"); diff --git a/flow-diff/src/test/resources/checkstyle_component_exclusions_concurrent.yaml b/flow-diff/src/test/resources/checkstyle_component_exclusions_concurrent.yaml index bd722a6..bf3f8b6 100644 --- a/flow-diff/src/test/resources/checkstyle_component_exclusions_concurrent.yaml +++ b/flow-diff/src/test/resources/checkstyle_component_exclusions_concurrent.yaml @@ -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"); diff --git a/flow-diff/src/test/resources/checkstyle_component_exclusions_prioritizer.yaml b/flow-diff/src/test/resources/checkstyle_component_exclusions_prioritizer.yaml index 26768a4..368e902 100644 --- a/flow-diff/src/test/resources/checkstyle_component_exclusions_prioritizer.yaml +++ b/flow-diff/src/test/resources/checkstyle_component_exclusions_prioritizer.yaml @@ -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"); diff --git a/flow-diff/src/test/resources/checkstyle_component_exclusions_selfloop.yaml b/flow-diff/src/test/resources/checkstyle_component_exclusions_selfloop.yaml index 68bb09d..13a21b7 100644 --- a/flow-diff/src/test/resources/checkstyle_component_exclusions_selfloop.yaml +++ b/flow-diff/src/test/resources/checkstyle_component_exclusions_selfloop.yaml @@ -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"); diff --git a/flow-diff/src/test/resources/checkstyle_controller_service_naming.yaml b/flow-diff/src/test/resources/checkstyle_controller_service_naming.yaml new file mode 100644 index 0000000..9e7f477 --- /dev/null +++ b/flow-diff/src/test/resources/checkstyle_controller_service_naming.yaml @@ -0,0 +1,23 @@ +# 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. + +include: + - controllerServiceNaming +rules: + controllerServiceNaming: + parameters: + patterns: + org.apache.nifi.dbcp.DBCPConnectionPool: "^acme_(dev|preprod|prod)_[a-z0-9_]+$" + defaultPattern: "^[a-z][a-z0-9_]+$" diff --git a/flow-diff/src/test/resources/checkstyle_emptyParameters.yaml b/flow-diff/src/test/resources/checkstyle_emptyParameters.yaml index 28431d8..2a52b6a 100644 --- a/flow-diff/src/test/resources/checkstyle_emptyParameters.yaml +++ b/flow-diff/src/test/resources/checkstyle_emptyParameters.yaml @@ -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"); diff --git a/flow-diff/src/test/resources/checkstyle_exclude.yaml b/flow-diff/src/test/resources/checkstyle_exclude.yaml index 4095ed4..6fae85b 100644 --- a/flow-diff/src/test/resources/checkstyle_exclude.yaml +++ b/flow-diff/src/test/resources/checkstyle_exclude.yaml @@ -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"); diff --git a/flow-diff/src/test/resources/checkstyle_limit1.yaml b/flow-diff/src/test/resources/checkstyle_limit1.yaml index 5fc1df5..570c717 100644 --- a/flow-diff/src/test/resources/checkstyle_limit1.yaml +++ b/flow-diff/src/test/resources/checkstyle_limit1.yaml @@ -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"); diff --git a/flow-diff/src/test/resources/checkstyle_naming_component_exclusions.yaml b/flow-diff/src/test/resources/checkstyle_naming_component_exclusions.yaml new file mode 100644 index 0000000..95425f5 --- /dev/null +++ b/flow-diff/src/test/resources/checkstyle_naming_component_exclusions.yaml @@ -0,0 +1,25 @@ +# 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. + +include: + - processorNaming +rules: + processorNaming: + parameters: + patterns: + org.apache.nifi.processors.standard.InvokeHTTP: ".*_EXT$" + componentExclusions: + "naming-test": + - "proc-invoke-http-001" diff --git a/flow-diff/src/test/resources/checkstyle_naming_overrides.yaml b/flow-diff/src/test/resources/checkstyle_naming_overrides.yaml new file mode 100644 index 0000000..129a08b --- /dev/null +++ b/flow-diff/src/test/resources/checkstyle_naming_overrides.yaml @@ -0,0 +1,24 @@ +# 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. + +include: + - processorNaming +rules: + processorNaming: + parameters: + defaultPattern: "^[A-Z][A-Z0-9_]+$" + overrides: + "naming-test": + defaultPattern: ".*" diff --git a/flow-diff/src/test/resources/checkstyle_override.yaml b/flow-diff/src/test/resources/checkstyle_override.yaml index 0e1a2ce..42503af 100644 --- a/flow-diff/src/test/resources/checkstyle_override.yaml +++ b/flow-diff/src/test/resources/checkstyle_override.yaml @@ -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"); diff --git a/flow-diff/src/test/resources/checkstyle_parameter_context_naming.yaml b/flow-diff/src/test/resources/checkstyle_parameter_context_naming.yaml new file mode 100644 index 0000000..05fb0f1 --- /dev/null +++ b/flow-diff/src/test/resources/checkstyle_parameter_context_naming.yaml @@ -0,0 +1,23 @@ +# 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. + +include: + - parameterContextNaming +rules: + parameterContextNaming: + parameters: + defaultPattern: "^acme_(dev|preprod|prod)_[a-z0-9_]+_(secrets|config)$" + exclude: + - "common_parameter_context" diff --git a/flow-diff/src/test/resources/checkstyle_parameter_provider_naming.yaml b/flow-diff/src/test/resources/checkstyle_parameter_provider_naming.yaml new file mode 100644 index 0000000..2249649 --- /dev/null +++ b/flow-diff/src/test/resources/checkstyle_parameter_provider_naming.yaml @@ -0,0 +1,22 @@ +# 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. + +include: + - parameterProviderNaming +rules: + parameterProviderNaming: + parameters: + patterns: + org.apache.nifi.parameter.aws.AwsSecretsManagerParameterProvider: "^acme_(dev|preprod|prod)_[a-z0-9_]+_secrets$" diff --git a/flow-diff/src/test/resources/checkstyle_processor_naming.yaml b/flow-diff/src/test/resources/checkstyle_processor_naming.yaml new file mode 100644 index 0000000..f3e8f6c --- /dev/null +++ b/flow-diff/src/test/resources/checkstyle_processor_naming.yaml @@ -0,0 +1,26 @@ +# 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. + +include: + - processorNaming +rules: + processorNaming: + parameters: + patterns: + org.apache.nifi.processors.standard.GenerateFlowFile: "^GENERATE_FLOW_FILE$" + org.apache.nifi.processors.attributes.UpdateAttribute: "^UPDATE_ATTRIBUTE$" + org.apache.nifi.processors.standard.InvokeHTTP: ".*_EXT$" + org.apache.nifi.processors.standard.ExecuteSQLRecord: ".*_(SEL|COPY|UPD_SEL|UPD_INS|EXT)$|^SP_.*" + org.apache.nifi.processors.snowflake.PutSnowflakeInternalStage: ".*_ISTG$" diff --git a/flow-diff/src/test/resources/checkstyle_processor_naming_default.yaml b/flow-diff/src/test/resources/checkstyle_processor_naming_default.yaml new file mode 100644 index 0000000..cbba14d --- /dev/null +++ b/flow-diff/src/test/resources/checkstyle_processor_naming_default.yaml @@ -0,0 +1,21 @@ +# 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. + +include: + - processorNaming +rules: + processorNaming: + parameters: + defaultPattern: "^[A-Z][A-Z0-9_]+$" diff --git a/flow-diff/src/test/resources/flow_v7_naming.json b/flow-diff/src/test/resources/flow_v7_naming.json new file mode 100644 index 0000000..893b87a --- /dev/null +++ b/flow-diff/src/test/resources/flow_v7_naming.json @@ -0,0 +1,385 @@ +{ + "externalControllerServices" : { }, + "flow" : { + "createdTimestamp" : 1726000168945, + "description" : "naming test", + "identifier" : "naming-test", + "lastModifiedTimestamp" : 1726000168945, + "name" : "naming-test", + "versionCount" : 0 + }, + "flowContents" : { + "comments" : "", + "componentType" : "PROCESS_GROUP", + "connections" : [ { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "proc-update-attr-001", + "name" : "UPDATE_ATTRIBUTE", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "conn-001", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "proc-gen-ff-001", + "name" : "GENERATE_FLOW_FILE", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + }, { + "backPressureDataSizeThreshold" : "1 GB", + "backPressureObjectThreshold" : 10000, + "bends" : [ ], + "componentType" : "CONNECTION", + "destination" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "proc-invoke-http-001", + "name" : "InvokeHTTP", + "type" : "PROCESSOR" + }, + "flowFileExpiration" : "0 sec", + "groupIdentifier" : "flow-contents-group", + "identifier" : "conn-002", + "labelIndex" : 0, + "loadBalanceCompression" : "DO_NOT_COMPRESS", + "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE", + "name" : "", + "partitioningAttribute" : "", + "prioritizers" : [ ], + "selectedRelationships" : [ "success" ], + "source" : { + "comments" : "", + "groupId" : "flow-contents-group", + "id" : "proc-update-attr-001", + "name" : "UPDATE_ATTRIBUTE", + "type" : "PROCESSOR" + }, + "zIndex" : 0 + } ], + "controllerServices" : [ { + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-dbcp-service-nar", + "group" : "org.apache.nifi", + "version" : "2.2.0" + }, + "comments" : "", + "componentType" : "CONTROLLER_SERVICE", + "groupIdentifier" : "flow-contents-group", + "identifier" : "cs-dbcp-valid-001", + "name" : "acme_prod_mssql_mds", + "properties" : { }, + "propertyDescriptors" : { }, + "scheduledState" : "ENABLED", + "type" : "org.apache.nifi.dbcp.DBCPConnectionPool" + }, { + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-dbcp-service-nar", + "group" : "org.apache.nifi", + "version" : "2.2.0" + }, + "comments" : "", + "componentType" : "CONTROLLER_SERVICE", + "groupIdentifier" : "flow-contents-group", + "identifier" : "cs-dbcp-invalid-001", + "name" : "Snowconnection_DEV", + "properties" : { }, + "propertyDescriptors" : { }, + "scheduledState" : "ENABLED", + "type" : "org.apache.nifi.dbcp.DBCPConnectionPool" + }, { + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-record-serialization-services-nar", + "group" : "org.apache.nifi", + "version" : "2.2.0" + }, + "comments" : "", + "componentType" : "CONTROLLER_SERVICE", + "groupIdentifier" : "flow-contents-group", + "identifier" : "cs-csv-writer-001", + "name" : "csv_record_writer", + "properties" : { }, + "propertyDescriptors" : { }, + "scheduledState" : "ENABLED", + "type" : "org.apache.nifi.csv.CSVRecordSetWriter" + } ], + "defaultBackPressureDataSizeThreshold" : "1 GB", + "defaultBackPressureObjectThreshold" : 10000, + "defaultFlowFileExpiration" : "0 sec", + "executionEngine" : "INHERITED", + "externalControllerServiceReferences" : { }, + "flowFileConcurrency" : "UNBOUNDED", + "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE", + "funnels" : [ ], + "identifier" : "flow-contents-group", + "inputPorts" : [ ], + "labels" : [ ], + "maxConcurrentTasks" : 1, + "name" : "NamingTestFlow", + "outputPorts" : [ ], + "parameterContextName" : "acme_prod_postgres_rbs_inventorydb_secrets", + "position" : { + "x" : 0.0, + "y" : 0.0 + }, + "processGroups" : [ ], + "processors" : [ { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-standard-nar", + "group" : "org.apache.nifi", + "version" : "2.2.0" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "proc-gen-ff-001", + "maxBackoffPeriod" : "10 mins", + "name" : "GENERATE_FLOW_FILE", + "penaltyDuration" : "30 sec", + "position" : { + "x" : 0.0, + "y" : 0.0 + }, + "properties" : { + "Batch Size" : "1" + }, + "propertyDescriptors" : { }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "1 min", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.standard.GenerateFlowFile", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-update-attribute-nar", + "group" : "org.apache.nifi", + "version" : "2.2.0" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "proc-update-attr-001", + "maxBackoffPeriod" : "10 mins", + "name" : "UPDATE_ATTRIBUTE", + "penaltyDuration" : "30 sec", + "position" : { + "x" : 200.0, + "y" : 0.0 + }, + "properties" : { }, + "propertyDescriptors" : { }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "0 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.attributes.UpdateAttribute", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ "Response", "No Retry", "Retry", "Original", "Failure" ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-standard-nar", + "group" : "org.apache.nifi", + "version" : "2.2.0" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "proc-invoke-http-001", + "maxBackoffPeriod" : "10 mins", + "name" : "InvokeHTTP", + "penaltyDuration" : "30 sec", + "position" : { + "x" : 400.0, + "y" : 0.0 + }, + "properties" : { + "HTTP Method" : "GET", + "HTTP URL" : "http://localhost" + }, + "propertyDescriptors" : { }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "0 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.standard.InvokeHTTP", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-standard-nar", + "group" : "org.apache.nifi", + "version" : "2.2.0" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "proc-exec-sql-001", + "maxBackoffPeriod" : "10 mins", + "name" : "CUSTOMER_SEL", + "penaltyDuration" : "30 sec", + "position" : { + "x" : 600.0, + "y" : 0.0 + }, + "properties" : { }, + "propertyDescriptors" : { }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "0 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.standard.ExecuteSQLRecord", + "yieldDuration" : "1 sec" + }, { + "autoTerminatedRelationships" : [ ], + "backoffMechanism" : "PENALIZE_FLOWFILE", + "bulletinLevel" : "WARN", + "bundle" : { + "artifact" : "nifi-snowflake-nar", + "group" : "org.apache.nifi", + "version" : "2.2.0" + }, + "comments" : "", + "componentType" : "PROCESSOR", + "concurrentlySchedulableTaskCount" : 1, + "executionNode" : "ALL", + "groupIdentifier" : "flow-contents-group", + "identifier" : "proc-put-stage-001", + "maxBackoffPeriod" : "10 mins", + "name" : "CUSTOMER_ISTG", + "penaltyDuration" : "30 sec", + "position" : { + "x" : 800.0, + "y" : 0.0 + }, + "properties" : { }, + "propertyDescriptors" : { }, + "retriedRelationships" : [ ], + "retryCount" : 10, + "runDurationMillis" : 0, + "scheduledState" : "ENABLED", + "schedulingPeriod" : "0 sec", + "schedulingStrategy" : "TIMER_DRIVEN", + "style" : { }, + "type" : "org.apache.nifi.processors.snowflake.PutSnowflakeInternalStage", + "yieldDuration" : "1 sec" + } ], + "remoteProcessGroups" : [ ], + "scheduledState" : "ENABLED", + "statelessFlowTimeout" : "1 min" + }, + "flowEncodingVersion" : "1.0", + "latest" : false, + "parameterContexts" : { + "acme_prod_postgres_rbs_inventorydb_secrets" : { + "componentType" : "PARAMETER_CONTEXT", + "inheritedParameterContexts" : [ ], + "name" : "acme_prod_postgres_rbs_inventorydb_secrets", + "parameters" : [ { + "description" : "", + "name" : "db.password", + "provided" : false, + "sensitive" : true + } ] + }, + "Test Parameter Context" : { + "componentType" : "PARAMETER_CONTEXT", + "inheritedParameterContexts" : [ ], + "name" : "Test Parameter Context", + "parameters" : [ { + "description" : "", + "name" : "some.param", + "provided" : false, + "sensitive" : false, + "value" : "some-value" + } ] + }, + "common_parameter_context" : { + "componentType" : "PARAMETER_CONTEXT", + "inheritedParameterContexts" : [ ], + "name" : "common_parameter_context", + "parameters" : [ { + "description" : "", + "name" : "shared.param", + "provided" : false, + "sensitive" : false, + "value" : "shared-value" + } ] + } + }, + "parameterProviders" : { + "pp-valid-001" : { + "identifier" : "pp-valid-001", + "name" : "acme_prod_mssql_landmark_secrets", + "type" : "org.apache.nifi.parameter.aws.AwsSecretsManagerParameterProvider", + "bundle" : { + "artifact" : "nifi-aws-parameter-provider-nar", + "group" : "org.apache.nifi", + "version" : "2.2.0" + } + }, + "pp-invalid-001" : { + "identifier" : "pp-invalid-001", + "name" : "Acme_Prod_Mssql_Secrets", + "type" : "org.apache.nifi.parameter.aws.AwsSecretsManagerParameterProvider", + "bundle" : { + "artifact" : "nifi-aws-parameter-provider-nar", + "group" : "org.apache.nifi", + "version" : "2.2.0" + } + } + }, + "snapshotMetadata" : { + "author" : "test@example.com", + "flowIdentifier" : "naming-test", + "timestamp" : 0 + } +}