Skip to content

feat: Billing for static ip#6292

Open
phot0n wants to merge 7 commits into
frappe:developfrom
phot0n:static-ip-charge
Open

feat: Billing for static ip#6292
phot0n wants to merge 7 commits into
frappe:developfrom
phot0n:static-ip-charge

Conversation

@phot0n
Copy link
Copy Markdown
Member

@phot0n phot0n commented Apr 29, 2026

Cases:

  • attach-detach (on the same vm)
  • vm terminated (if say vm was moved to another provider or was in general terminated)
  • say the static ip was moved to another vm (attach-detach cycle of original vm has to be tracked and the attach-detach of the new vm as well)

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

This PR introduces a Static IP Log doctype to record when a static IP is attached/detached from a server for billing purposes, adds an is_static_ip field to Database Server, and extends the virtual_machine.update_servers sync to include Database Server. The core billing decorator in static_ip_log.py has attribute-name mismatches that will prevent any log entries from being created in practice.

  • P1 – Log entries never created: create_static_ip_log_if_applicable checks self.cloud_provider (doesn't exist; should be self.provider) and reads self.static_ip / self.get(\"static_ip\") (doesn't exist; should be self.ip / self.is_static_ip). The condition will never evaluate to True, so no Static IP Log record is ever inserted.
  • P1 – OCI silently not logged: The decorator only handles \"AWS EC2\"; the get_oci_static_ip path successfully assigns a reserved IP and syncs the VM but produces no billing log.
  • P2 – Decorator order with @frappe.whitelist(): The inner wrapper(self) does not accept *args, **kwargs, which can cause TypeError if Frappe passes extra arguments to whitelisted methods.

Confidence Score: 3/5

Not safe to merge — the central billing decorator has attribute-name bugs that prevent any log entries from being recorded, making the feature a no-op.

Two P1 findings mean the feature will silently not work: no Static IP Log record is ever created because the decorator references non-existent attributes (self.cloud_provider, self.static_ip). OCI static IPs are also unlogged. The doctype schema and VM sync changes are correct, but the core logic is broken.

press/press/doctype/static_ip_log/static_ip_log.py — the decorator create_static_ip_log_if_applicable needs attribute name fixes before this feature will function

Important Files Changed

Filename Overview
press/press/doctype/static_ip_log/static_ip_log.py Core billing logic for Static IP; decorator uses wrong attribute names (cloud_provider vs provider, static_ip vs ip/is_static_ip) causing log entries to never be created and OCI support to be silently missing
press/press/doctype/server/server.py Adds @create_static_ip_log_if_applicable decorator to get_static_ip; decorator ordering with @frappe.whitelist() may cause TypeErrors if Frappe passes kwargs
press/press/doctype/virtual_machine/virtual_machine.py Adds Database Server to the list of doctypes that sync is_static_ip from VirtualMachine — straightforward and correct
press/press/doctype/database_server/database_server.json Adds is_static_ip Check field and get_static_ip action button to Database Server doctype — schema change looks correct
press/press/doctype/static_ip_log/static_ip_log.json New Static IP Log doctype definition with server, server_type, static_ip, and status fields; looks correct
press/press/doctype/static_ip_log/test_static_ip_log.py Empty test class — no test cases implemented for the new billing feature

Sequence Diagram

sequenceDiagram
    participant Admin
    participant Server/DBServer
    participant Decorator as create_static_ip_log_if_applicable
    participant VirtualMachine
    participant StaticIPLog

    Admin->>Server/DBServer: get_static_ip() [whitelisted]
    Server/DBServer->>Decorator: wrapper(self)
    Decorator->>Decorator: static_ip = self.get("static_ip") [wrong field, returns None]
    Decorator->>Server/DBServer: fn(self) → get_aws_static_ip() / get_oci_static_ip()
    Server/DBServer->>VirtualMachine: vm_doc.sync()
    VirtualMachine->>Server/DBServer: update_servers() sets is_static_ip, ip
    Decorator->>Decorator: check self.cloud_provider == "AWS EC2" [wrong attr, never True]
    Note over Decorator,StaticIPLog: Log never created due to wrong attribute names
    Decorator-->>Admin: return result
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: press/press/doctype/static_ip_log/static_ip_log.py
Line: 45-50

Comment:
**`cloud_provider` and `static_ip` attributes don't exist on Server/DatabaseServer**

The decorator accesses `self.cloud_provider` but the actual field on both `Server` and `Database Server` is `self.provider`. Since `self.cloud_provider` will raise `AttributeError` (or silently evaluate to falsy via `getattr`), the condition on line 45 will never match `"AWS EC2"` and **no log entry will ever be created**.

Additionally, `self.static_ip` and `self.get("static_ip")` reference a non-existent field. The IP address is stored in `self.ip`, and the flag field is `is_static_ip` (a `Check`). The log will always be created with an empty `static_ip` value and incorrect status even if the provider check were fixed.

```python
# line 42 – should capture IP before the call
static_ip = self.get("ip")
result = fn(self)

# line 45 – use self.provider, not self.cloud_provider
if self.provider == "AWS EC2":
    create_static_ip_log(
        server=self.name,
        server_type=self.doctype,
        static_ip=self.ip or static_ip,
        status="Attached" if self.is_static_ip else "Detached",
    )
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: press/press/doctype/static_ip_log/static_ip_log.py
Line: 40-55

Comment:
**OCI static IP assignment silently not logged**

The function call at line 43 (`result = fn(self)`) can raise an exception (e.g. if AWS `allocate_address` fails or `frappe.throw` is called for unsupported providers). The decorator only logs for `"AWS EC2"`; `get_static_ip` also calls `get_oci_static_ip` which successfully assigns a reserved IP and syncs the VM, yet the decorator produces no billing log for OCI. Static IP billing for OCI is silently missing.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: press/press/doctype/static_ip_log/static_ip_log.py
Line: 23-25

Comment:
**Validation blocks future server types without graceful handling**

The `validate` hard-codes the allowed `server_type` values to `"Server"` and `"Database Server"`. If the billing feature is ever extended to `"Proxy Server"` or `"NAT Server"` (both of which already have `is_static_ip` synced in `virtual_machine.py`), records created through the decorator will fail validation. Consider removing the guard (the `server_type` field already has a `Link` to DocType for referential integrity) or using a configurable allowlist.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: press/press/doctype/server/server.py
Line: 2562-2563

Comment:
**Decorator order may break `@frappe.whitelist()` calls with kwargs**

The decorator stack is:
```python
@frappe.whitelist()
@create_static_ip_log_if_applicable
def get_static_ip(self): ...
```
The inner `wrapper(self)` only accepts `self`, but Frappe may pass additional keyword arguments to whitelisted methods. If any caller passes extra arguments the call will fail with a `TypeError`. The wrapper should accept `*args, **kwargs` and forward them to `fn`.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(server): add log to track static ip..." | Re-trigger Greptile

Comment on lines +45 to +50
if self.cloud_provider == "AWS EC2":
create_static_ip_log(
server=self.name,
server_type=self.doctype,
static_ip=self.static_ip or static_ip,
status="Attached" if self.static_ip else "Detached",
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.

P1 cloud_provider and static_ip attributes don't exist on Server/DatabaseServer

The decorator accesses self.cloud_provider but the actual field on both Server and Database Server is self.provider. Since self.cloud_provider will raise AttributeError (or silently evaluate to falsy via getattr), the condition on line 45 will never match "AWS EC2" and no log entry will ever be created.

Additionally, self.static_ip and self.get("static_ip") reference a non-existent field. The IP address is stored in self.ip, and the flag field is is_static_ip (a Check). The log will always be created with an empty static_ip value and incorrect status even if the provider check were fixed.

# line 42 – should capture IP before the call
static_ip = self.get("ip")
result = fn(self)

# line 45 – use self.provider, not self.cloud_provider
if self.provider == "AWS EC2":
    create_static_ip_log(
        server=self.name,
        server_type=self.doctype,
        static_ip=self.ip or static_ip,
        status="Attached" if self.is_static_ip else "Detached",
    )
Prompt To Fix With AI
This is a comment left during a code review.
Path: press/press/doctype/static_ip_log/static_ip_log.py
Line: 45-50

Comment:
**`cloud_provider` and `static_ip` attributes don't exist on Server/DatabaseServer**

The decorator accesses `self.cloud_provider` but the actual field on both `Server` and `Database Server` is `self.provider`. Since `self.cloud_provider` will raise `AttributeError` (or silently evaluate to falsy via `getattr`), the condition on line 45 will never match `"AWS EC2"` and **no log entry will ever be created**.

Additionally, `self.static_ip` and `self.get("static_ip")` reference a non-existent field. The IP address is stored in `self.ip`, and the flag field is `is_static_ip` (a `Check`). The log will always be created with an empty `static_ip` value and incorrect status even if the provider check were fixed.

```python
# line 42 – should capture IP before the call
static_ip = self.get("ip")
result = fn(self)

# line 45 – use self.provider, not self.cloud_provider
if self.provider == "AWS EC2":
    create_static_ip_log(
        server=self.name,
        server_type=self.doctype,
        static_ip=self.ip or static_ip,
        status="Attached" if self.is_static_ip else "Detached",
    )
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +40 to +55
def create_static_ip_log_if_applicable(fn):
def wrapper(self):
static_ip = self.get("static_ip")
result = fn(self)

if self.cloud_provider == "AWS EC2":
create_static_ip_log(
server=self.name,
server_type=self.doctype,
static_ip=self.static_ip or static_ip,
status="Attached" if self.static_ip else "Detached",
)

return result

return wrapper
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.

P1 OCI static IP assignment silently not logged

The function call at line 43 (result = fn(self)) can raise an exception (e.g. if AWS allocate_address fails or frappe.throw is called for unsupported providers). The decorator only logs for "AWS EC2"; get_static_ip also calls get_oci_static_ip which successfully assigns a reserved IP and syncs the VM, yet the decorator produces no billing log for OCI. Static IP billing for OCI is silently missing.

Prompt To Fix With AI
This is a comment left during a code review.
Path: press/press/doctype/static_ip_log/static_ip_log.py
Line: 40-55

Comment:
**OCI static IP assignment silently not logged**

The function call at line 43 (`result = fn(self)`) can raise an exception (e.g. if AWS `allocate_address` fails or `frappe.throw` is called for unsupported providers). The decorator only logs for `"AWS EC2"`; `get_static_ip` also calls `get_oci_static_ip` which successfully assigns a reserved IP and syncs the VM, yet the decorator produces no billing log for OCI. Static IP billing for OCI is silently missing.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +23 to +25
def validate(self):
if self.server_type not in ("Server", "Database Server"):
frappe.throw("Server Type must be either 'Server' or 'Database Server'")
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.

P2 Validation blocks future server types without graceful handling

The validate hard-codes the allowed server_type values to "Server" and "Database Server". If the billing feature is ever extended to "Proxy Server" or "NAT Server" (both of which already have is_static_ip synced in virtual_machine.py), records created through the decorator will fail validation. Consider removing the guard (the server_type field already has a Link to DocType for referential integrity) or using a configurable allowlist.

Prompt To Fix With AI
This is a comment left during a code review.
Path: press/press/doctype/static_ip_log/static_ip_log.py
Line: 23-25

Comment:
**Validation blocks future server types without graceful handling**

The `validate` hard-codes the allowed `server_type` values to `"Server"` and `"Database Server"`. If the billing feature is ever extended to `"Proxy Server"` or `"NAT Server"` (both of which already have `is_static_ip` synced in `virtual_machine.py`), records created through the decorator will fail validation. Consider removing the guard (the `server_type` field already has a `Link` to DocType for referential integrity) or using a configurable allowlist.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread press/press/doctype/server/server.py Outdated
Comment on lines +2562 to +2563
@frappe.whitelist()
@create_static_ip_log_if_applicable
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.

P2 Decorator order may break @frappe.whitelist() calls with kwargs

The decorator stack is:

@frappe.whitelist()
@create_static_ip_log_if_applicable
def get_static_ip(self): ...

The inner wrapper(self) only accepts self, but Frappe may pass additional keyword arguments to whitelisted methods. If any caller passes extra arguments the call will fail with a TypeError. The wrapper should accept *args, **kwargs and forward them to fn.

Prompt To Fix With AI
This is a comment left during a code review.
Path: press/press/doctype/server/server.py
Line: 2562-2563

Comment:
**Decorator order may break `@frappe.whitelist()` calls with kwargs**

The decorator stack is:
```python
@frappe.whitelist()
@create_static_ip_log_if_applicable
def get_static_ip(self): ...
```
The inner `wrapper(self)` only accepts `self`, but Frappe may pass additional keyword arguments to whitelisted methods. If any caller passes extra arguments the call will fail with a `TypeError`. The wrapper should accept `*args, **kwargs` and forward them to `fn`.

How can I resolve this? If you propose a fix, please make it concise.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 29, 2026

Codecov Report

❌ Patch coverage is 68.00000% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.41%. Comparing base (7285b85) to head (85ef904).
⚠️ Report is 25 commits behind head on develop.

Files with missing lines Patch % Lines
press/press/doctype/static_ip_log/static_ip_log.py 52.94% 8 Missing ⚠️

❌ Your patch check has failed because the patch coverage (68.00%) is below the target coverage (75.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #6292      +/-   ##
===========================================
+ Coverage    56.33%   56.41%   +0.08%     
===========================================
  Files          936      938       +2     
  Lines        77710    77935     +225     
  Branches       525      525              
===========================================
+ Hits         43779    43970     +191     
- Misses       33903    33937      +34     
  Partials        28       28              
Flag Coverage Δ
dashboard 90.75% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@phot0n phot0n force-pushed the static-ip-charge branch from 85ef904 to 5ef10e7 Compare May 6, 2026 23:13
@phot0n phot0n marked this pull request as ready for review May 6, 2026 23:16
@phot0n phot0n requested a review from shadrak98 as a code owner May 6, 2026 23:16
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Comment thread press/press/doctype/static_ip_log/static_ip_log.py Outdated
Co-authored-by: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com>
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