Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cmd/obol/sell.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v3"
"gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func sellCommand(cfg *config.Config) *cli.Command {
Expand Down Expand Up @@ -1236,7 +1237,7 @@ func explorerTxURL(network, txHash string) string {
// or "Disabled", non-failure paths
// ⚠ Status=False — failure / blocked
// ⏳ Status=Unknown / empty — pending
func formatConditionLine(cond monetizeapi.Condition) string {
func formatConditionLine(cond metav1.Condition) string {
icon := conditionIcon(cond)
parts := []string{cond.Type}
if cond.Reason != "" {
Expand All @@ -1252,7 +1253,7 @@ func formatConditionLine(cond monetizeapi.Condition) string {
// conditionIcon picks a glyph based on the condition's status + reason. The
// glyphs are plain unicode (no lipgloss) so the function is safe to call from
// pure unit tests; coloring is applied at print time via the ui package.
func conditionIcon(cond monetizeapi.Condition) string {
func conditionIcon(cond metav1.Condition) string {
switch cond.Status {
case "True":
switch cond.Reason {
Expand Down Expand Up @@ -2105,7 +2106,7 @@ func explorerBaseURL(canonicalName string) string {
}

// isConditionTrue reports whether the named condition exists with Status=True.
func isConditionTrue(conds []monetizeapi.Condition, name string) bool {
func isConditionTrue(conds []metav1.Condition, name string) bool {
for _, c := range conds {
if c.Type == name {
return c.Status == "True"
Expand Down
19 changes: 10 additions & 9 deletions cmd/obol/sell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
x402verifier "github.com/ObolNetwork/obol-stack/internal/x402"
"github.com/urfave/cli/v3"
"gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ─────────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -315,7 +316,7 @@ func TestServiceOfferStatusLines(t *testing.T) {
Endpoint: "/services/demo",
AgentID: "5008",
RegistrationTxHash: "0xabc",
Conditions: []monetizeapi.Condition{
Conditions: []metav1.Condition{
{Type: "Registered", Status: "True", Reason: "Registered", Message: "Published registration document and recorded agent 5008"},
},
},
Expand Down Expand Up @@ -455,15 +456,15 @@ func TestExplorerTxURL(t *testing.T) {
func TestConditionIcon(t *testing.T) {
tests := []struct {
name string
cond monetizeapi.Condition
cond metav1.Condition
want string
}{
{"true succeeded", monetizeapi.Condition{Status: "True", Reason: "Reconciled"}, "✓"},
{"true skipped", monetizeapi.Condition{Status: "True", Reason: "Skipped"}, "ℹ"},
{"true disabled", monetizeapi.Condition{Status: "True", Reason: "Disabled"}, "ℹ"},
{"false failed", monetizeapi.Condition{Status: "False", Reason: "Unhealthy"}, "⚠"},
{"unknown pending", monetizeapi.Condition{Status: "Unknown"}, "⏳"},
{"empty pending", monetizeapi.Condition{}, "⏳"},
{"true succeeded", metav1.Condition{Status: "True", Reason: "Reconciled"}, "✓"},
{"true skipped", metav1.Condition{Status: "True", Reason: "Skipped"}, "ℹ"},
{"true disabled", metav1.Condition{Status: "True", Reason: "Disabled"}, "ℹ"},
{"false failed", metav1.Condition{Status: "False", Reason: "Unhealthy"}, "⚠"},
{"unknown pending", metav1.Condition{Status: "Unknown"}, "⏳"},
{"empty pending", metav1.Condition{}, "⏳"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -504,7 +505,7 @@ func TestAgentRegistryNFTURL(t *testing.T) {
}

func TestIsConditionTrue(t *testing.T) {
conds := []monetizeapi.Condition{
conds := []metav1.Condition{
{Type: "Ready", Status: "True"},
{Type: "Registered", Status: "False"},
{Type: "Pending", Status: "Unknown"},
Expand Down
2 changes: 1 addition & 1 deletion internal/embed/embed_crd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func TestServiceOfferCRD_PrinterColumns(t *testing.T) {
t.Fatal("additionalPrinterColumns missing or wrong type")
}

expected := []string{"Type", "Model", "Price", "Network", "Ready", "Age"}
expected := []string{"Type", "Model", "Price", "Network", "Ready", "Paused", "Age"}
if len(cols) != len(expected) {
t.Fatalf("got %d printer columns, want %d", len(cols), len(expected))
}
Expand Down
41 changes: 36 additions & 5 deletions internal/embed/infrastructure/base/templates/agent-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,32 +106,63 @@ spec:
properties:
conditions:
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: Last time the condition transitioned.
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: Human-readable message with details.
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: Machine-readable reason for the condition.
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: Status of the condition.
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: Condition type.
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
endpoint:
description: |-
Cluster-internal URL for the agent runtime (e.g.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,32 +174,63 @@ spec:
properties:
conditions:
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: Last time the condition transitioned.
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: Human-readable message with details.
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: Machine-readable reason for the condition.
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: Status of the condition.
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: Condition type.
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
observedGeneration:
format: int64
type: integer
Expand Down
60 changes: 54 additions & 6 deletions internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ spec:
- jsonPath: .status.conditions[?(@.type=="Ready")].status
name: Ready
type: string
- jsonPath: .status.conditions[?(@.type=="Paused")].status
name: Paused
priority: 1
type: boolean
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
Expand Down Expand Up @@ -101,6 +105,19 @@ spec:
description: URL path prefix for the HTTPRoute, defaults to /services/<name>.
pattern: ^/[a-zA-Z0-9/_.-]*$
type: string
paused:
default: false
description: |-
Paused, when true, signals the controller to tear down the
published route and stop serving traffic for this offer. The
ServiceOffer CR itself is preserved (annotations, status,
payment config) so resume is a single-byte spec flip.

Compare Deployment.spec.paused, CronJob.spec.suspend — typed
spec fields are the K8s api-conventions canonical way to gate
reconciler behaviour. The legacy obol.org/paused annotation is
still honoured for one release; see IsPaused().
type: boolean
payment:
properties:
asset:
Expand Down Expand Up @@ -319,34 +336,65 @@ spec:
conditions:
description: |-
Condition types: ModelReady, UpstreamHealthy, PaymentGateReady,
RoutePublished, Registered, Ready.
RoutePublished, Registered, Paused, Ready.
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: Last time the condition transitioned.
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: Human-readable message with details.
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: Machine-readable reason for the condition.
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: Status of the condition.
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: Condition type.
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
endpoint:
description: The public endpoint URL once the route is published.
type: string
Expand Down
Loading