Skip to content

CCSD-676 changes#387

Open
nitish-egov wants to merge 5 commits into
masterfrom
CCSD-676-nitish
Open

CCSD-676 changes#387
nitish-egov wants to merge 5 commits into
masterfrom
CCSD-676-nitish

Conversation

@nitish-egov
Copy link
Copy Markdown

@nitish-egov nitish-egov commented Aug 21, 2025

Summary by CodeRabbit

  • Bug Fixes

    • Vacant-land tax estimation now tolerates multiple matching rate configurations by using the first match and errors only when no rate exists.
    • Prevented translation and label runtime errors by adding null-safety guards for missing category/type data, avoiding crashes and preserving mappings.
  • Chores

    • Added a parameterized backend build workflow for multi-architecture image builds and multi-arch manifest publishing.
    • Updated build container setup to use multi-arch base images and streamlined packaging/runtime steps.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 21, 2025

Walkthrough

Vacant-land handling in EstimationService now selects the first matching billing slab (errors only if none); TranslationService adds null-guards before splitting propertyType, usageCategory, ownershipCategory, and unit usageCategory; new GitHub Actions backend build workflow was added; Dockerfile base images and build flow were updated for multi-arch builds.

Changes

Cohort / File(s) Summary
Vacant land slab selection logic
property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/EstimationService.java
For vacant land: removed strict "exactly one slab" enforcement; if no matching slabs, throw a "No billing slab found for vacant land" error; otherwise use the first matching slab (index 0) and compute tax as unitRate * landArea. Non-vacant flow unchanged except added null-safety guards.
Null-guards in translation mapping
property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/TranslationService.java
Add null checks before splitting propertyType, usageCategory, ownershipCategory, and per-unit usageCategory; perform subtype/major/minor mappings only when source strings and master arrays exist; small control-flow/bracing adjustments.
CI workflow for multi-arch builds
.github/workflows/backendbuild.yaml
New parameterized GitHub Actions workflow: workflow_dispatch inputs for branch/service, resolves build config, sets dockerfile/work-dir/tag outputs, builds/pushes app and optional DB images for amd64/arm64, and creates multi-arch manifests.
Dockerfile: multi-arch build/runtime bases
build/maven/Dockerfile
Switched build stage to maven:3.9.6-amazoncorretto-17, simplified mvn package flow; runtime base changed to amazoncorretto:17-alpine; adjusted artifact copy, start.sh handling (dos2unix, chmod), and added a runtime architecture check.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant EstimationService
  participant BillingSlabRepo

  Client->>EstimationService: getEstimationMap(property)
  alt property is Vacant Land
    EstimationService->>BillingSlabRepo: query matching slabs
    BillingSlabRepo-->>EstimationService: matchingSlabs[]
    alt matchingSlabs empty
      EstimationService-->>Client: throw "No billing slab found for vacant land"
    else matchingSlabs non-empty
      EstimationService->>EstimationService: select first slab (index 0)
      EstimationService->>EstimationService: tax = unitRate * landArea
      EstimationService-->>Client: return estimation with tax
    end
  else not Vacant Land
    EstimationService-->>Client: return estimation per existing non-vacant flow
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I’m a rabbit in the code’s bright glade,
I pick the first slab where shadows fade.
If none are there, I stamp my paw —
“No slab!” I cry, then munch a straw.
Hop, compute, and off I trot 🥕🐇

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The pull request title "CCSD-676 changes" is a vague reference to a ticket ID without describing the actual changes made in the changeset. Replace the title with a descriptive summary of the main changes, such as 'Add null-safety checks and fix vacant land billing slab handling' or a similarly specific description of the key modifications.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch CCSD-676-nitish

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b3df56a and d8aab92.

📒 Files selected for processing (1)
  • property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/EstimationService.java (1 hunks)

Comment on lines +191 to +193
if (filteredBillingSlabs.isEmpty())
throw new CustomException(PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANCT, "No billing slab found for vacant land");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Error code constant looks misspelled and may not exist; verify and standardize.

The code uses PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANCT (note “VACANCT”). If this constant does not exist, this won’t compile. Recommend PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANT (or reuse an existing standard error code used for slab mismatches) and include tenantId/propertyId in the message for observability.

Run the following to verify constants and downstream consumption of billingSlabIds:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Check if the error code constants exist
rg -nP '\bPT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANCT\b|\bPT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANT\b' -C2

# 2) Inspect where billingSlabIds is consumed and whether callers expect "id|index" format
rg -nP '\bbillingSlabIds\b' -C3

# 3) Confirm we already define a land-area-null error code (adjust if needed)
rg -nP '\bPT_ESTIMATE_LANDAREA_NULL\b|\bLAND.?AREA.*NULL\b' -C2 --type=java

Length of output: 61575


Fix typo in error code constant & standardize exception

The constant PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANCT is defined (so it compiles) but its name is misspelled and its value is a generic code. Please:

  • Rename the constant in CalculatorConstants.java (around line 285):
    • From:
      public static final String PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANCT = "PT_ESTIMATE_BILLINGSLABS_UNMATCH";
    • To:
      public static final String PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANT = "PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANT";
  • Update its usage in EstimationService.java (line 192):
    • From:
      throw new CustomException(PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANCT, 
                                "No billing slab found for vacant land");
    • To:
      throw new CustomException(PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANT,
        String.format("No billing slab found for vacant land [tenant=%s, propertyId=%s]", 
                      tenantId, detail.getPropertyId()));
  • (Optional) If you prefer to keep the generic code string, you can still rename the constant name for clarity and add a new, specific message constant for the vacant‐land case.
  • There is currently no PT_ESTIMATE_LANDAREA_NULL constant—if you need to guard against null land‐area values, define and throw a dedicated error code (e.g. PT_ESTIMATE_LANDAREA_NULL) before calculating tax.

@egovernments egovernments deleted a comment from coderabbitai Bot Sep 22, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/TranslationService.java (5)

66-69: Potential NPE: address/locality accessed without null checks

property.getAddress() or .getLocality() can be null; this will crash. Guard and still emit an empty locality object for schema stability.

-        Map<String, Object> addressMap = new HashMap();
-        Map<String, Object> localityMap = new HashMap();
-        localityMap.put("area",property.getAddress().getLocality().getArea());
-        localityMap.put("code",property.getAddress().getLocality().getCode());
-        addressMap.put("locality",localityMap);
+        Map<String, Object> addressMap = new HashMap<>();
+        Map<String, Object> localityMap = new HashMap<>();
+        addressMap.put("locality", localityMap);
+        if (property.getAddress() != null && property.getAddress().getLocality() != null) {
+            localityMap.put("area", property.getAddress().getLocality().getArea());
+            localityMap.put("code", property.getAddress().getLocality().getCode());
+        }

134-134: Potential NPE: unit.getConstructionDetail() may be null

Null construction detail will crash when reading built-up area. Guard it.

-                unitMap.put("unitArea", unit.getConstructionDetail().getBuiltUpArea());
+                if (unit.getConstructionDetail() != null) {
+                    unitMap.put("unitArea", unit.getConstructionDetail().getBuiltUpArea());
+                }

120-126: Guard owners iteration against null

If property.getOwners() is null, this will crash. Keep payload stable by emitting empty list when absent.

-        property.getOwners().forEach(ownerInfo -> {
-            Map<String, Object> owner = mapper.convertValue(ownerInfo,  new TypeReference<Map<String, Object>>() {});
-            owners.add(owner);
-        });
+        if (!CollectionUtils.isEmpty(property.getOwners())) {
+            property.getOwners().forEach(ownerInfo -> {
+                Map<String, Object> owner = mapper.convertValue(ownerInfo, new TypeReference<Map<String, Object>>() {});
+                owners.add(owner);
+            });
+        }

162-165: Prefer decimalValue() over doubleValue() to avoid precision loss

Money amounts should not go through double. Read BigDecimal directly from the JSON node.

-                    BigDecimal adhocExemption = new BigDecimal(assessment.getAdditionalDetails().get(ADHOC_REBATE_KEY).doubleValue());
+                    BigDecimal adhocExemption = assessment.getAdditionalDetails().get(ADHOC_REBATE_KEY).decimalValue();
...
-                    BigDecimal adhocPenalty = new BigDecimal(assessment.getAdditionalDetails().get(ADHOC_PENALTY_KEY).doubleValue());
+                    BigDecimal adhocPenalty = assessment.getAdditionalDetails().get(ADHOC_PENALTY_KEY).decimalValue();

Also applies to: 171-174


61-65: Eliminate raw HashMap usage

Use the diamond operator to avoid unchecked-conversion warnings.

-        Map<String, Object> propertyMap = new HashMap();
+        Map<String, Object> propertyMap = new HashMap<>();
...
-        Map<String, Object> addressMap = new HashMap();
-        Map<String, Object> localityMap = new HashMap();
+        Map<String, Object> addressMap = new HashMap<>();
+        Map<String, Object> localityMap = new HashMap<>();
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8aab92 and 52db42c.

📒 Files selected for processing (1)
  • property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/TranslationService.java (2 hunks)

Comment on lines +80 to +85
if(property.getPropertyType() != null) {
String[] propertyTypeMasterData = property.getPropertyType().split("\\.");
propertyType = propertyTypeMasterData[0];
if(propertyTypeMasterData.length > 1)
propertySubType = propertyTypeMasterData[1];
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Avoid empty subtype on trailing dots; trim and write only non-blank parts

Current split can write "" when value is "FOO." or whitespace. Write only non-blank, trimmed segments.

-        if(property.getPropertyType() != null) {
-            String[] propertyTypeMasterData = property.getPropertyType().split("\\.");
-            propertyType = propertyTypeMasterData[0];
-            if(propertyTypeMasterData.length > 1)
-                propertySubType = propertyTypeMasterData[1];
-        }
+        if (StringUtils.hasText(property.getPropertyType())) {
+            String[] parts = property.getPropertyType().trim().split("\\.", -1);
+            propertyType = StringUtils.hasText(parts[0]) ? parts[0].trim() : null;
+            if (parts.length > 1 && StringUtils.hasText(parts[1])) {
+                propertySubType = parts[1].trim();
+            }
+        }

Add import (outside this hunk):

import org.springframework.util.StringUtils;
🤖 Prompt for AI Agents
In
property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/TranslationService.java
around lines 80 to 85, the current split on property.getPropertyType() can
produce empty or whitespace-only segments (e.g. "FOO."), so trim and only assign
non-blank parts: after splitting, trim each segment and use
StringUtils.hasText(...) to set propertyType and propertySubType only when the
trimmed segment contains text; if the first non-blank segment exists set
propertyType, and if a second non-blank segment exists set propertySubType. Also
add the import "import org.springframework.util.StringUtils;" at the top of the
file (outside this hunk).

🧹 Nitpick

DRY: extract a small helper to split dot-codes safely

Four blocks repeat the same split-and-assign pattern. A tiny helper reduces bugs and keeps behavior consistent.

Example helper (outside this class’ methods):

private static List<String> safeSegments(String code, int maxSegments) {
    if (!StringUtils.hasText(code)) return Collections.emptyList();
    String[] parts = code.trim().split("\\.", -1);
    List<String> out = new ArrayList<>(Math.min(parts.length, maxSegments));
    for (int i = 0; i < Math.min(parts.length, maxSegments); i++) {
        if (StringUtils.hasText(parts[i])) out.add(parts[i].trim());
        else out.add(null);
    }
    return out;
}

Then use safeSegments(code, 2) for property/usage/ownership and safeSegments(code, 4) for units, writing only non-null items to the map.

Also applies to: 88-93, 96-101, 138-152

🤖 Prompt for AI Agents
In
property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/TranslationService.java
around lines 80-85 (also applies to 88-93, 96-101, 138-152), the same
dot-splitting and index-safe assignment pattern is duplicated; extract a small
reusable helper (e.g., safeSegments(code, maxSegments)) that trims, handles
null/empty, splits with a limit, and returns a predictable list/array of segment
values (including nulls for empty segments) and replace each duplicated block to
call that helper and assign segments[0], segments[1], etc., only when present;
ensure the helper is static/private at class level, use it with the appropriate
maxSegments for each use-site, and update the assignments to use the helper
output to keep behavior consistent and avoid IndexOutOfBounds or inconsistent
trimming.

Comment on lines +88 to +93
if(property.getUsageCategory() != null) {
String[] usageCategoryMasterData = property.getUsageCategory().split("\\.");
usageCategoryMajor = usageCategoryMasterData[0];
if(usageCategoryMasterData.length > 1)
usageCategoryMinor = usageCategoryMasterData[1];
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Same handling for usageCategory: ignore blanks and trailing dots

Prevent writing empty strings into major/minor.

-        if(property.getUsageCategory() != null) {
-            String[] usageCategoryMasterData = property.getUsageCategory().split("\\.");
-            usageCategoryMajor = usageCategoryMasterData[0];
-            if(usageCategoryMasterData.length > 1)
-                usageCategoryMinor = usageCategoryMasterData[1];
-        }
+        if (StringUtils.hasText(property.getUsageCategory())) {
+            String[] parts = property.getUsageCategory().trim().split("\\.", -1);
+            usageCategoryMajor = StringUtils.hasText(parts[0]) ? parts[0].trim() : null;
+            if (parts.length > 1 && StringUtils.hasText(parts[1])) {
+                usageCategoryMinor = parts[1].trim();
+            }
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(property.getUsageCategory() != null) {
String[] usageCategoryMasterData = property.getUsageCategory().split("\\.");
usageCategoryMajor = usageCategoryMasterData[0];
if(usageCategoryMasterData.length > 1)
usageCategoryMinor = usageCategoryMasterData[1];
}
if (StringUtils.hasText(property.getUsageCategory())) {
String[] parts = property.getUsageCategory().trim().split("\\.", -1);
usageCategoryMajor = StringUtils.hasText(parts[0]) ? parts[0].trim() : null;
if (parts.length > 1 && StringUtils.hasText(parts[1])) {
usageCategoryMinor = parts[1].trim();
}
}
🤖 Prompt for AI Agents
In
property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/TranslationService.java
around lines 88 to 93, the current usageCategory split can assign empty strings
to usageCategoryMajor/Minor when input contains blanks or trailing/leading dots;
update the logic to ignore empty segments: trim the usageCategory, split on
"\\.", then iterate segments to pick the first non-empty token as
usageCategoryMajor and the next non-empty token as usageCategoryMinor (skip
empty tokens produced by consecutive/trailing/leading dots); ensure you only
assign when the token has text (use StringUtils.hasText or
token.trim().isEmpty() check) so blanks are never written into major/minor.

Comment on lines +96 to +101
if(property.getOwnershipCategory() != null) {
String[] ownershipCategoryMasterData = property.getOwnershipCategory().split("\\.");
ownershipCategory = ownershipCategoryMasterData[0];
if(ownershipCategoryMasterData.length > 1)
subOwnershipCategory = ownershipCategoryMasterData[1];
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Same handling for ownershipCategory/subOwnershipCategory

Avoid empty strings when input has trailing dot or whitespace.

-        if(property.getOwnershipCategory() != null) {
-            String[] ownershipCategoryMasterData = property.getOwnershipCategory().split("\\.");
-            ownershipCategory = ownershipCategoryMasterData[0];
-            if(ownershipCategoryMasterData.length > 1)
-                subOwnershipCategory = ownershipCategoryMasterData[1];
-        }
+        if (StringUtils.hasText(property.getOwnershipCategory())) {
+            String[] parts = property.getOwnershipCategory().trim().split("\\.", -1);
+            ownershipCategory = StringUtils.hasText(parts[0]) ? parts[0].trim() : null;
+            if (parts.length > 1 && StringUtils.hasText(parts[1])) {
+                subOwnershipCategory = parts[1].trim();
+            }
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(property.getOwnershipCategory() != null) {
String[] ownershipCategoryMasterData = property.getOwnershipCategory().split("\\.");
ownershipCategory = ownershipCategoryMasterData[0];
if(ownershipCategoryMasterData.length > 1)
subOwnershipCategory = ownershipCategoryMasterData[1];
}
if (StringUtils.hasText(property.getOwnershipCategory())) {
String[] parts = property.getOwnershipCategory().trim().split("\\.", -1);
ownershipCategory = StringUtils.hasText(parts[0]) ? parts[0].trim() : null;
if (parts.length > 1 && StringUtils.hasText(parts[1])) {
subOwnershipCategory = parts[1].trim();
}
}
🤖 Prompt for AI Agents
In
property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/TranslationService.java
around lines 96-101, the current split on ownershipCategory can produce empty
strings for trailing dots or surrounding whitespace; trim the raw value first,
return early if empty, then split on "\\.", filter out any empty/blank tokens
(trimming each token) and assign ownershipCategory = first token and
subOwnershipCategory = second token only if it exists and is non-empty; ensure
no empty strings are assigned.

Comment on lines +138 to +152
if(unit.getUsageCategory() != null) {
String[] masterData = unit.getUsageCategory().split("\\.");

if(masterData.length >= 1)
unitMap.put("usageCategoryMajor", masterData[0]);
if(masterData.length >= 1)
unitMap.put("usageCategoryMajor", masterData[0]);

if(masterData.length >= 2)
unitMap.put("usageCategoryMinor", masterData[1]);
if(masterData.length >= 2)
unitMap.put("usageCategoryMinor", masterData[1]);

if(masterData.length >= 3)
unitMap.put("usageCategorySubMinor", masterData[2]);
if(masterData.length >= 3)
unitMap.put("usageCategorySubMinor", masterData[2]);

if(masterData.length >= 4)
unitMap.put("usageCategoryDetail",masterData[3]);
if(masterData.length >= 4)
unitMap.put("usageCategoryDetail",masterData[3]);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Unit usageCategory: write only non-blank segments; trim input

Prevents empty values when usageCategory has trailing dots or blanks; reduces noisy payloads.

-                if(unit.getUsageCategory() != null) {
-                    String[] masterData = unit.getUsageCategory().split("\\.");
-
-                    if(masterData.length >= 1)
-                        unitMap.put("usageCategoryMajor", masterData[0]);
-
-                    if(masterData.length >= 2)
-                        unitMap.put("usageCategoryMinor", masterData[1]);
-
-                    if(masterData.length >= 3)
-                        unitMap.put("usageCategorySubMinor", masterData[2]);
-
-                    if(masterData.length >= 4)
-                        unitMap.put("usageCategoryDetail",masterData[3]);
-                }
+                if (StringUtils.hasText(unit.getUsageCategory())) {
+                    String[] parts = unit.getUsageCategory().trim().split("\\.", -1);
+                    if (parts.length > 0 && StringUtils.hasText(parts[0])) unitMap.put("usageCategoryMajor", parts[0].trim());
+                    if (parts.length > 1 && StringUtils.hasText(parts[1])) unitMap.put("usageCategoryMinor", parts[1].trim());
+                    if (parts.length > 2 && StringUtils.hasText(parts[2])) unitMap.put("usageCategorySubMinor", parts[2].trim());
+                    if (parts.length > 3 && StringUtils.hasText(parts[3])) unitMap.put("usageCategoryDetail", parts[3].trim());
+                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(unit.getUsageCategory() != null) {
String[] masterData = unit.getUsageCategory().split("\\.");
if(masterData.length >= 1)
unitMap.put("usageCategoryMajor", masterData[0]);
if(masterData.length >= 1)
unitMap.put("usageCategoryMajor", masterData[0]);
if(masterData.length >= 2)
unitMap.put("usageCategoryMinor", masterData[1]);
if(masterData.length >= 2)
unitMap.put("usageCategoryMinor", masterData[1]);
if(masterData.length >= 3)
unitMap.put("usageCategorySubMinor", masterData[2]);
if(masterData.length >= 3)
unitMap.put("usageCategorySubMinor", masterData[2]);
if(masterData.length >= 4)
unitMap.put("usageCategoryDetail",masterData[3]);
if(masterData.length >= 4)
unitMap.put("usageCategoryDetail",masterData[3]);
}
if (StringUtils.hasText(unit.getUsageCategory())) {
String[] parts = unit.getUsageCategory().trim().split("\\.", -1);
if (parts.length > 0 && StringUtils.hasText(parts[0])) unitMap.put("usageCategoryMajor", parts[0].trim());
if (parts.length > 1 && StringUtils.hasText(parts[1])) unitMap.put("usageCategoryMinor", parts[1].trim());
if (parts.length > 2 && StringUtils.hasText(parts[2])) unitMap.put("usageCategorySubMinor", parts[2].trim());
if (parts.length > 3 && StringUtils.hasText(parts[3])) unitMap.put("usageCategoryDetail", parts[3].trim());
}
🤖 Prompt for AI Agents
In
property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/TranslationService.java
around lines 138 to 152, the current logic splits usageCategory and writes
segments unconditionally which can produce empty values for trailing dots or
blanks; update it to trim the usageCategory before splitting, iterate over the
split segments and only put non-blank (after trim) segments into unitMap with
the correct keys (usageCategoryMajor, usageCategoryMinor, usageCategorySubMinor,
usageCategoryDetail), skipping any empty segments so no blank values are added
to the payload.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52db42c and e4e13ac.

📒 Files selected for processing (1)
  • property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/EstimationService.java (2 hunks)
🔇 Additional comments (1)
property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/EstimationService.java (1)

188-195: Vacant-land path: fix misspelled error code, guard null landArea, record billingSlabIds, and make selection deterministic.

  • The error code constant is misspelled (VACANCT). Prior review already flagged this. Rename to VACANT and update the message with identifiers.
  • Possible NPE when landArea is null.
  • Currently not adding the chosen slab to billingSlabIds for the vacant-land path.
  • Picking index 0 without ordering can be nondeterministic; sort slabs to ensure stable selection.

Apply this diff:

-        if (PT_TYPE_VACANT_LAND.equalsIgnoreCase(detail.getPropertyType())) {
-            if (filteredBillingSlabs.isEmpty())
-                throw new CustomException(PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANCT, "No billing slab found for vacant land");
-                
-            taxAmt = taxAmt.add(BigDecimal.valueOf(filteredBillingSlabs.get(0).getUnitRate() * detail.getLandArea()));
-        } else {
+        if (PT_TYPE_VACANT_LAND.equalsIgnoreCase(detail.getPropertyType())) {
+            if (filteredBillingSlabs.isEmpty()) {
+                throw new CustomException(PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANT,
+                        String.format("No billing slab found for vacant land [tenant=%s, propertyId=%s]", tenantId, property.getPropertyId()));
+            }
+            if (detail.getLandArea() == null) {
+                throw new CustomException(PT_ESTIMATE_LANDAREA_NULL, PT_ESTIMATE_LANDAREA_NULL_MSG);
+            }
+            filteredBillingSlabs.sort(Comparator.comparing(BillingSlab::getFromPlotSize));
+            BillingSlab chosen = filteredBillingSlabs.get(0);
+            taxAmt = taxAmt.add(BigDecimal.valueOf(chosen.getUnitRate() * detail.getLandArea()));
+            billingSlabIds.add(chosen.getId() + "|0");
+        } else {

Also add the following constants (in CalculatorConstants.java):

public static final String PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANT = "PT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANT";
public static final String PT_ESTIMATE_LANDAREA_NULL = "PT_ESTIMATE_LANDAREA_NULL";
public static final String PT_ESTIMATE_LANDAREA_NULL_MSG = "Land area is required for vacant land tax estimation";

Run to verify no stale usages and that constants exist:

#!/bin/bash
set -euo pipefail
rg -nP '\bPT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANCT\b' -C2 || true
rg -nP '\bPT_ESTIMATE_BILLINGSLABS_UNMATCH_VACANT\b' -C2
rg -nP '\bPT_ESTIMATE_LANDAREA_NULL(_MSG)?\b' -C2
rg -nP '\bbillingSlabIds\b' -C3

Comment on lines +1146 to +1151
if(property.getUsageCategory() != null) {
String[] usageCategoryMasterData = property.getUsageCategory().split("\\.");
usageCategoryMajor = usageCategoryMasterData[0];
if(usageCategoryMasterData.length > 1)
usageCategoryMinor = usageCategoryMasterData[1];
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Harden null-guard to also skip blanks for usageCategory.

Use isNotBlank to avoid empty token arrays for values like "".

-        if(property.getUsageCategory() != null) {
+        if (StringUtils.isNotBlank(property.getUsageCategory())) {
             String[] usageCategoryMasterData = property.getUsageCategory().split("\\.");
             usageCategoryMajor = usageCategoryMasterData[0];
             if(usageCategoryMasterData.length > 1)
                 usageCategoryMinor = usageCategoryMasterData[1];
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(property.getUsageCategory() != null) {
String[] usageCategoryMasterData = property.getUsageCategory().split("\\.");
usageCategoryMajor = usageCategoryMasterData[0];
if(usageCategoryMasterData.length > 1)
usageCategoryMinor = usageCategoryMasterData[1];
}
if (StringUtils.isNotBlank(property.getUsageCategory())) {
String[] usageCategoryMasterData = property.getUsageCategory().split("\\.");
usageCategoryMajor = usageCategoryMasterData[0];
if(usageCategoryMasterData.length > 1)
usageCategoryMinor = usageCategoryMasterData[1];
}

Comment on lines +1160 to +1164
String[] propertyTypeCollection = property.getPropertyType().split("\\.");
propertyType = propertyTypeCollection[0];
if(propertyTypeCollection.length > 1)
propertySubType = propertyTypeCollection[1];
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Same improvement for propertyType.

-        if(property.getPropertyType() != null) {
+        if (StringUtils.isNotBlank(property.getPropertyType())) {
             String[] propertyTypeCollection = property.getPropertyType().split("\\.");
             propertyType = propertyTypeCollection[0];
             if(propertyTypeCollection.length > 1)
                 propertySubType = propertyTypeCollection[1];
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String[] propertyTypeCollection = property.getPropertyType().split("\\.");
propertyType = propertyTypeCollection[0];
if(propertyTypeCollection.length > 1)
propertySubType = propertyTypeCollection[1];
}
if (StringUtils.isNotBlank(property.getPropertyType())) {
String[] propertyTypeCollection = property.getPropertyType().split("\\.");
propertyType = propertyTypeCollection[0];
if (propertyTypeCollection.length > 1)
propertySubType = propertyTypeCollection[1];
}
🤖 Prompt for AI Agents
In
property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/EstimationService.java
around lines 1160 to 1164, the split handling sets propertyType from
propertyTypeCollection[0] without ensuring the original value is preserved when
no split occurs; mirror the earlier improvement used for propertySubType by
guarding and falling back: ensure propertyType is set to the full
property.getPropertyType() when the split produces no parts or the first part is
empty (and trim values), and only assign propertyTypeCollection[1] to
propertySubType when length > 1; update the logic to validate null/empty input
and use a safe fallback to avoid losing the original type.

Comment on lines +1173 to +1177
String[] ownership = property.getOwnershipCategory().split("\\.");
ownershipCategory = ownership[0];
if(ownership.length > 1)
subownershipCategory = ownership[1];
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Same improvement for ownershipCategory.

-        if(property.getOwnershipCategory() != null) {
+        if (StringUtils.isNotBlank(property.getOwnershipCategory())) {
             String[] ownership = property.getOwnershipCategory().split("\\.");
             ownershipCategory = ownership[0];
             if(ownership.length > 1)
                 subownershipCategory = ownership[1];
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String[] ownership = property.getOwnershipCategory().split("\\.");
ownershipCategory = ownership[0];
if(ownership.length > 1)
subownershipCategory = ownership[1];
}
if (StringUtils.isNotBlank(property.getOwnershipCategory())) {
String[] ownership = property.getOwnershipCategory().split("\\.");
ownershipCategory = ownership[0];
if(ownership.length > 1)
subownershipCategory = ownership[1];
}
🤖 Prompt for AI Agents
In
property-tax/pt-calculator-v2/src/main/java/org/egov/pt/calculator/service/EstimationService.java
around lines 1173-1177, the code assigns ownershipCategory directly from the
split result which can cause NPEs or retain surrounding whitespace; update the
block to first null-check property.getOwnershipCategory(), then split and guard
indexes and trim values before assignment (e.g., if ownership.length > 0 set
ownershipCategory = ownership[0].trim(); if ownership.length > 1 set
subownershipCategory = ownership[1].trim()), ensuring safe handling of empty or
whitespace-only strings.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In @.github/workflows/backendbuild.yaml:
- Around line 123-151: The matrix job "build-matrix" currently computes the
"db_folder_exists" output in its "check-db-folder" step (id: check-db-folder),
which is non-deterministic across matrix legs; move this directory existence
check into the single-run job "resolve-config" so it runs once and add an
outputs entry (e.g., outputs.db_folder_exists) alongside the existing outputs
like dockerfile_path and work_dir, populate that output from the new check in
resolve-config, and remove the "Check for DB folder" step and the
db_folder_exists outputs block from "build-matrix" so downstream jobs consume
needs.resolve-config.outputs.db_folder_exists deterministically.
- Around line 157-160: Update the docker/login-action version from v2 to v3 in
the workflow; locate the steps that use "uses: docker/login-action@v2" inside
the "build-matrix" and "create-manifest" jobs and change them to
"docker/login-action@v3" so both occurrences (the step under build-matrix and
the step under create-manifest) are updated to v3 to resolve the Node 16
deprecation issue.
- Around line 65-68: Remove the extra spaces after the colon in the outputs
mapping so each key uses a single space before its value; update the outputs
block (keys dockerfile_path, work_dir, tag) to use the form "key: ${{ ... }}"
instead of aligning with multiple spaces (e.g., change "work_dir:        ${{
steps.pick_dockerfile.outputs.work_dir }}" to "work_dir: ${{
steps.pick_dockerfile.outputs.work_dir }}" and similarly for dockerfile_path and
tag).
- Around line 71-73: Replace occurrences of the checkout action pinned to
actions/checkout@v3 with actions/checkout@v4 in the workflow jobs that use it:
update the checkout step inside the resolve-config job and the checkout step
inside the build-matrix job so both reference actions/checkout@v4 (ensure the
`uses:` lines that currently say actions/checkout@v3 are changed to
actions/checkout@v4).

In `@build/maven/Dockerfile`:
- Line 19: The COPY line "COPY --from=build /app/target/*.jar /app/start.sh
/opt/egov/" is too broad and may grab -sources, -javadoc, or other jars; update
the Dockerfile to copy a specific artifact name instead (e.g., the Spring Boot
fat JAR) or a tighter glob, or change the Maven build to set a deterministic
finalName via spring-boot-maven-plugin and then reference that exact filename in
the COPY command so only the intended runnable jar is copied.
- Line 22: The Dockerfile currently runs "dos2unix /opt/egov/start.sh" in an
Alpine-based image which lacks dos2unix; fix by either installing it before use
(add an apk add --no-cache dos2unix step prior to the RUN that references
dos2unix) or replace the dos2unix invocation with an equivalent sed conversion
(e.g., a sed command to remove CRLFs) so that the RUN line referencing
/opt/egov/start.sh succeeds; update the RUN that contains "dos2unix
/opt/egov/start.sh && chmod +x /opt/egov/start.sh" accordingly.
- Around line 24-25: Remove the build-time architecture check by deleting the
RUN uname -m instruction from the Dockerfile (the line that executes "RUN uname
-m"); this command only prints the build agent's architecture during image
build, creates an extra layer, and serves no runtime verification purpose, so
remove that RUN line to simplify the image and reduce layers.
- Around line 14-27: The runtime Dockerfile currently leaves the container
running as root; add a non-root user and switch to it in the runtime stage by
creating a group/user (e.g., with addgroup/adduser or adduser -D on alpine),
chowning /opt/egov and the copied artifacts (so /opt/egov/start.sh and the
jar(s) are owned by that user), and then add a USER <username> directive before
CMD ["/opt/egov/start.sh"]; ensure the user has execute permission on start.sh
(chmod +x already present) and that any required runtime ports/dirs under
/opt/egov are writable by the new user.

Comment on lines +65 to +68
outputs:
dockerfile_path: ${{ steps.pick_dockerfile.outputs.dockerfile_path }}
work_dir: ${{ steps.pick_dockerfile.outputs.work_dir }}
tag: ${{ steps.tag.outputs.tag }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Fix YAML formatting: extra spaces after colons.

YAMLlint flags Lines 67–68 for excessive whitespace. This is a minor lint issue but worth cleaning up for consistency.

Proposed fix
-      dockerfile_path: ${{ steps.pick_dockerfile.outputs.dockerfile_path }}
-      work_dir:        ${{ steps.pick_dockerfile.outputs.work_dir }}
-      tag:             ${{ steps.tag.outputs.tag }}
+      dockerfile_path: ${{ steps.pick_dockerfile.outputs.dockerfile_path }}
+      work_dir: ${{ steps.pick_dockerfile.outputs.work_dir }}
+      tag: ${{ steps.tag.outputs.tag }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
outputs:
dockerfile_path: ${{ steps.pick_dockerfile.outputs.dockerfile_path }}
work_dir: ${{ steps.pick_dockerfile.outputs.work_dir }}
tag: ${{ steps.tag.outputs.tag }}
outputs:
dockerfile_path: ${{ steps.pick_dockerfile.outputs.dockerfile_path }}
work_dir: ${{ steps.pick_dockerfile.outputs.work_dir }}
tag: ${{ steps.tag.outputs.tag }}
🧰 Tools
🪛 YAMLlint (1.38.0)

[error] 67-67: too many spaces after colon

(colons)


[error] 68-68: too many spaces after colon

(colons)

🤖 Prompt for AI Agents
In @.github/workflows/backendbuild.yaml around lines 65 - 68, Remove the extra
spaces after the colon in the outputs mapping so each key uses a single space
before its value; update the outputs block (keys dockerfile_path, work_dir, tag)
to use the form "key: ${{ ... }}" instead of aligning with multiple spaces
(e.g., change "work_dir:        ${{ steps.pick_dockerfile.outputs.work_dir }}"
to "work_dir: ${{ steps.pick_dockerfile.outputs.work_dir }}" and similarly for
dockerfile_path and tag).

Comment on lines +71 to +73
uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.branch }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

actions/checkout v3 Node 16 EOL GitHub Actions 2025

💡 Result:

  • actions/checkout@v3 is a JavaScript action built for the Node.js 16 runtime. GitHub Actions ended support for “Node16 actions” by removing Node 16 from the hosted runner image on November 12, 2024, meaning workflows that still rely on Node16-based actions (like checkout@v3) can break and must be updated. [1]
  • GitHub began the enforcement/migration earlier: they announced the Node16→Node20 transition in September 2023 (warnings starting Oct 23, 2023) [2], and then announced that runners would enforce Node20 instead of Node16 starting June 3, 2024 (with temporary opt-out env vars). [3]
  • The practical fix is to upgrade to actions/checkout@v4 (Node20-based) if you haven’t already. [2][3]
  • Looking ahead into 2025+: GitHub has also already started the Node20 deprecation process (plan to move actions to Node24; Node24 default begins March 4, 2026). So staying current on major versions (v4 → future v5, etc.) is the safe path. [4]

Sources: [1] [2] [3] [4]


🌐 Web query:

actions/checkout v4 release date Node version

💡 Result:

  • actions/checkout v4.0.0 release date: October 17, 2023 (tag v4.0.0 was published “17 Oct” on the official GitHub release page). [1][2]
  • Node version used by actions/checkout v4: Node.js 20 (the v4.0.0 release notes say “Update default runtime to node20”). [1][3]

Sources:
[1] (github.com)
[2] (releasealert.dev)
[3] (newreleases.io)

Citations:


🌐 Web query:

actionlint actions/checkout v3 deprecated warning

💡 Result:

actionlint is warning because actions/checkout@v3 is a JavaScript action built for the Node.js 16 runtime, and GitHub Actions has deprecated Node 16 and moved workflows toward newer Node runtimes (Node 20 became the default in mid‑2024). This is why checkout@v3 is flagged as “deprecated/outdated” in workflows. [1][2][3]

Fix: upgrade to actions/checkout@v4 (or pin to the v4 commit SHA).

steps:
  - uses: actions/checkout@v4

If you still see the warning after updating checkout, check other @v3 actions in the workflow (e.g., upload-artifact@v3, cache@v3, etc.) and bump them to their Node 20+ major versions too. [1]

Sources: [1] GitHub Changelog (Node16→Node20 transition), [2] actions/checkout@v3 uses Node16 discussion, [3] updated Node20 default date.


Update actions/checkout from v3 to v4 — Node 16 support was removed from runners.

actions/checkout@v3 uses Node.js 16, which GitHub removed from hosted runners on November 12, 2024. Workflows relying on v3 are already broken. Update to actions/checkout@v4 (Node 20) in both the resolve-config (Line 71) and build-matrix (Line 140) jobs.

Proposed fix
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4

Apply in both locations (Lines 71 and 140).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.branch }}
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch }}
🧰 Tools
🪛 actionlint (1.7.10)

[error] 71-71: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🤖 Prompt for AI Agents
In @.github/workflows/backendbuild.yaml around lines 71 - 73, Replace
occurrences of the checkout action pinned to actions/checkout@v3 with
actions/checkout@v4 in the workflow jobs that use it: update the checkout step
inside the resolve-config job and the checkout step inside the build-matrix job
so both reference actions/checkout@v4 (ensure the `uses:` lines that currently
say actions/checkout@v3 are changed to actions/checkout@v4).

Comment on lines +123 to +151
build-matrix:
needs: resolve-config
# expose whether a DB folder was present
outputs:
db_folder_exists: ${{ steps.check-db-folder.outputs.folder_exists }}
strategy:
matrix:
include:
- arch: amd64
platform: linux/amd64
runner: ubuntu-latest
- arch: arm64
platform: linux/arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.branch }}

- name: Check for DB folder
id: check-db-folder
run: |
if [ -d "${{ needs.resolve-config.outputs.work_dir }}/src/main/resources/db" ]; then
echo "folder_exists=true" >> "$GITHUB_OUTPUT"
else
echo "folder_exists=false" >> "$GITHUB_OUTPUT"
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Matrix job output db_folder_exists is non-deterministic.

The build-matrix job runs as a 2×1 matrix (amd64 + arm64). When both matrix legs set the db_folder_exists output, GitHub Actions picks the value from the last leg to complete, which is non-deterministic. In practice this works because both legs check the same source tree, but it's fragile — if matrix composition changes, this could silently break.

Consider moving the DB-folder check into resolve-config (which runs once) and passing it as an output alongside dockerfile_path and work_dir.

🧰 Tools
🪛 actionlint (1.7.10)

[error] 140-140: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🤖 Prompt for AI Agents
In @.github/workflows/backendbuild.yaml around lines 123 - 151, The matrix job
"build-matrix" currently computes the "db_folder_exists" output in its
"check-db-folder" step (id: check-db-folder), which is non-deterministic across
matrix legs; move this directory existence check into the single-run job
"resolve-config" so it runs once and add an outputs entry (e.g.,
outputs.db_folder_exists) alongside the existing outputs like dockerfile_path
and work_dir, populate that output from the new check in resolve-config, and
remove the "Check for DB folder" step and the db_folder_exists outputs block
from "build-matrix" so downstream jobs consume
needs.resolve-config.outputs.db_folder_exists deterministically.

Comment on lines +157 to +160
uses: docker/login-action@v2
with:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_PASSWORD }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update docker/login-action from v2 to v3.

Same Node 16 deprecation issue. docker/login-action@v2 should be updated to v3 in both the build-matrix (Line 157) and create-manifest (Line 194) jobs.

Proposed fix
-        uses: docker/login-action@v2
+        uses: docker/login-action@v3

Apply in both locations (Lines 157 and 194).

Also applies to: 194-197

🧰 Tools
🪛 actionlint (1.7.10)

[error] 157-157: the runner of "docker/login-action@v2" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🤖 Prompt for AI Agents
In @.github/workflows/backendbuild.yaml around lines 157 - 160, Update the
docker/login-action version from v2 to v3 in the workflow; locate the steps that
use "uses: docker/login-action@v2" inside the "build-matrix" and
"create-manifest" jobs and change them to "docker/login-action@v3" so both
occurrences (the step under build-matrix and the step under create-manifest) are
updated to v3 to resolve the Node 16 deprecation issue.

Comment thread build/maven/Dockerfile
Comment on lines +14 to 27
FROM amazoncorretto:17-alpine

WORKDIR /opt/egov

# Copy artifacts from the build stage
COPY --from=build /app/target/*.jar /app/start.sh /opt/egov/

RUN chmod +x /opt/egov/start.sh
# Ensure the start script has correct line endings and is executable
RUN dos2unix /opt/egov/start.sh && chmod +x /opt/egov/start.sh

# Verify architecture inside the container
RUN uname -m

CMD ["/opt/egov/start.sh"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Container runs as root — add a non-root USER.

The runtime stage has no USER directive, so the container runs as root. This is flagged by both Trivy (DS002) and Checkov (CKV_DOCKER_3). Create a non-root user for the runtime stage.

🛡️ Proposed fix
 FROM amazoncorretto:17-alpine
 
 WORKDIR /opt/egov
 
 # Copy artifacts from the build stage
 COPY --from=build /app/target/*.jar /app/start.sh /opt/egov/
 
-# Ensure the start script has correct line endings and is executable
-RUN dos2unix /opt/egov/start.sh && chmod +x /opt/egov/start.sh
+# Ensure the start script has correct line endings and is executable, and create non-root user
+RUN sed -i 's/\r$//' /opt/egov/start.sh && chmod +x /opt/egov/start.sh \
+    && addgroup -S egov && adduser -S egov -G egov
 
-# Verify architecture inside the container
-RUN uname -m
+USER egov
 
 CMD ["/opt/egov/start.sh"]
🧰 Tools
🪛 Checkov (3.2.334)

[low] 1-27: Ensure that HEALTHCHECK instructions have been added to container images

(CKV_DOCKER_2)


[low] 1-27: Ensure that a user for the container has been created

(CKV_DOCKER_3)

🪛 Trivy (0.58.1)

[warning] 18-18: ':latest' tag used

Specify a tag in the 'FROM' statement for image 'egovio/8-openjdk-alpine'

Rule: DS001

Learn more

(IaC/Dockerfile)

🤖 Prompt for AI Agents
In `@build/maven/Dockerfile` around lines 14 - 27, The runtime Dockerfile
currently leaves the container running as root; add a non-root user and switch
to it in the runtime stage by creating a group/user (e.g., with addgroup/adduser
or adduser -D on alpine), chowning /opt/egov and the copied artifacts (so
/opt/egov/start.sh and the jar(s) are owned by that user), and then add a USER
<username> directive before CMD ["/opt/egov/start.sh"]; ensure the user has
execute permission on start.sh (chmod +x already present) and that any required
runtime ports/dirs under /opt/egov are writable by the new user.

Comment thread build/maven/Dockerfile
WORKDIR /opt/egov

# Copy artifacts from the build stage
COPY --from=build /app/target/*.jar /app/start.sh /opt/egov/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Wildcard JAR copy may pull in unintended artifacts.

/app/target/*.jar will copy every JAR in the target directory (including -sources.jar, -javadoc.jar, or dependency JARs copied by certain plugins). Consider using the Maven spring-boot-maven-plugin final name or a more specific glob to ensure only the intended fat JAR is copied.

🤖 Prompt for AI Agents
In `@build/maven/Dockerfile` at line 19, The COPY line "COPY --from=build
/app/target/*.jar /app/start.sh /opt/egov/" is too broad and may grab -sources,
-javadoc, or other jars; update the Dockerfile to copy a specific artifact name
instead (e.g., the Spring Boot fat JAR) or a tighter glob, or change the Maven
build to set a deterministic finalName via spring-boot-maven-plugin and then
reference that exact filename in the COPY command so only the intended runnable
jar is copied.

Comment thread build/maven/Dockerfile

RUN chmod +x /opt/egov/start.sh
# Ensure the start script has correct line endings and is executable
RUN dos2unix /opt/egov/start.sh && chmod +x /opt/egov/start.sh
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

dos2unix is not available in Alpine-based images by default — build will fail.

amazoncorretto:17-alpine is Alpine-based and does not ship dos2unix. This RUN step will fail with a "not found" error. Either install it first (apk add --no-cache dos2unix) or use sed as a lightweight alternative.

🐛 Proposed fix

Option A — install dos2unix:

-RUN dos2unix /opt/egov/start.sh && chmod +x /opt/egov/start.sh
+RUN apk add --no-cache dos2unix \
+    && dos2unix /opt/egov/start.sh \
+    && chmod +x /opt/egov/start.sh

Option B — use sed (no extra package):

-RUN dos2unix /opt/egov/start.sh && chmod +x /opt/egov/start.sh
+RUN sed -i 's/\r$//' /opt/egov/start.sh && chmod +x /opt/egov/start.sh
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN dos2unix /opt/egov/start.sh && chmod +x /opt/egov/start.sh
RUN apk add --no-cache dos2unix \
&& dos2unix /opt/egov/start.sh \
&& chmod +x /opt/egov/start.sh
Suggested change
RUN dos2unix /opt/egov/start.sh && chmod +x /opt/egov/start.sh
RUN sed -i 's/\r$//' /opt/egov/start.sh && chmod +x /opt/egov/start.sh
🤖 Prompt for AI Agents
In `@build/maven/Dockerfile` at line 22, The Dockerfile currently runs "dos2unix
/opt/egov/start.sh" in an Alpine-based image which lacks dos2unix; fix by either
installing it before use (add an apk add --no-cache dos2unix step prior to the
RUN that references dos2unix) or replace the dos2unix invocation with an
equivalent sed conversion (e.g., a sed command to remove CRLFs) so that the RUN
line referencing /opt/egov/start.sh succeeds; update the RUN that contains
"dos2unix /opt/egov/start.sh && chmod +x /opt/egov/start.sh" accordingly.

Comment thread build/maven/Dockerfile
Comment on lines +24 to +25
# Verify architecture inside the container
RUN uname -m
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Build-time uname -m serves no runtime purpose — remove it.

This only prints the architecture of the build agent during image build. It doesn't verify the running container's architecture and adds an unnecessary layer.

Proposed fix
-# Verify architecture inside the container
-RUN uname -m
-
 CMD ["/opt/egov/start.sh"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Verify architecture inside the container
RUN uname -m
CMD ["/opt/egov/start.sh"]
🤖 Prompt for AI Agents
In `@build/maven/Dockerfile` around lines 24 - 25, Remove the build-time
architecture check by deleting the RUN uname -m instruction from the Dockerfile
(the line that executes "RUN uname -m"); this command only prints the build
agent's architecture during image build, creates an extra layer, and serves no
runtime verification purpose, so remove that RUN line to simplify the image and
reduce layers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants