Skip to content
Merged
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
22 changes: 22 additions & 0 deletions configs/adapter-config-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ spec:
# Global params
# ============================================================================
# params to extract from CloudEvent and environment variables
#
# SUPPORTED TYPES:
# ================
# - string: Default, any value converted to string
# - int/int64: Integer value (strings parsed, floats truncated)
# - float/float64: Floating point value
# - bool: Boolean (supports: true/false, yes/no, on/off, 1/0)
#
params:
# Environment variables from deployment
- name: "hyperfleetApiBaseUrl"
Expand All @@ -94,6 +102,20 @@ spec:
description: "Unique identifier for the target cluster"
required: true

# Example: Extract and convert to int
# - name: "nodeCount"
# source: "event.spec.nodeCount"
# type: "int"
# default: 3
# description: "Number of nodes in the cluster"

# Example: Extract and convert to bool
# - name: "enableFeature"
# source: "env.ENABLE_FEATURE"
# type: "bool"
# default: false
# description: "Enable experimental feature"


# ============================================================================
# Global Preconditions
Expand Down
13 changes: 9 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.25.0
require (
github.com/cloudevents/sdk-go/v2 v2.16.2
github.com/docker/go-connections v0.6.0
github.com/go-playground/validator/v10 v10.30.1
github.com/google/cel-go v0.26.1
github.com/mitchellh/copystructure v1.2.0
github.com/openshift-hyperfleet/hyperfleet-broker v1.0.1
Expand Down Expand Up @@ -58,12 +59,15 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
Expand All @@ -77,6 +81,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.10 // indirect
Expand Down Expand Up @@ -128,13 +133,13 @@ require (
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/net v0.45.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/term v0.36.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/term v0.38.0 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/api v0.243.0 // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
Expand Down
28 changes: 20 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
Expand All @@ -108,6 +110,14 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
Expand Down Expand Up @@ -182,6 +192,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
Expand Down Expand Up @@ -344,8 +356,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
Expand All @@ -363,8 +375,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
Expand All @@ -385,10 +397,10 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
Expand Down
107 changes: 93 additions & 14 deletions internal/config_loader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,22 @@ The `config_loader` package loads and validates HyperFleet Adapter configuration
## Features

- **YAML Parsing**: Load configurations from files or bytes
- **Validation**: Required fields, structure, CEL expressions, K8s manifests
- **Type Safety**: Strongly-typed Go structs
- **Structural Validation**: Required fields, formats, enums via `go-playground/validator`
- **Semantic Validation**: CEL expressions, template variables, K8s manifests
- **Type Safety**: Strongly-typed Go structs with struct embedding
- **Helper Methods**: Query params, resources, preconditions by name

## Package Structure

| File | Purpose |
|------|---------|
| `loader.go` | Load configs from file/bytes, resolve file references |
| `types.go` | All type definitions with validation tags |
| `validator.go` | Orchestrates structural + semantic validation |
| `struct_validator.go` | `go-playground/validator` integration |
| `accessors.go` | Helper methods for querying config |
| `constants.go` | Field names, API versions, regex patterns |

## Usage

```go
Expand Down Expand Up @@ -63,19 +75,52 @@ See `configs/adapter-config-template.yaml` for the complete configuration refere

## Validation

The loader validates:
- Required fields (`apiVersion`, `kind`, `metadata.name`, `adapter.version`)
- HTTP methods in API calls (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`)
- Parameters have `source`
- File references exist (`buildRef`, `manifest.ref`)
- CEL expressions are syntactically valid
- K8s manifests have required fields
- CaptureField has either `field` or `expression` (not both, not neither)
### Two-Phase Validation

1. **Structural Validation** (`ValidateStructure`)
- Uses `go-playground/validator` with struct tags
- Required fields, enum values, mutual exclusivity
- Custom validators: `resourcename`, `validoperator`

2. **Semantic Validation** (`ValidateSemantic`)
- CEL expression syntax
- Template variable references
- Condition value types
- K8s manifest required fields

### Validation Tags

```go
// Required field
Name string `yaml:"name" validate:"required"`

// Enum validation
Method string `yaml:"method" validate:"required,oneof=GET POST PUT PATCH DELETE"`

// Mutual exclusivity (field OR expression, not both)
Field string `yaml:"field,omitempty" validate:"required_without=Expression,excluded_with=Expression"`
Expression string `yaml:"expression,omitempty" validate:"required_without=Field,excluded_with=Field"`

// Custom validators
Name string `yaml:"name" validate:"required,resourcename"`
Operator string `yaml:"operator" validate:"required,validoperator"`
```

### Custom Validators

| Tag | Purpose |
|-----|---------|
| `resourcename` | CEL-compatible names (lowercase start, no hyphens) |
| `validoperator` | Valid condition operators (eq, neq, in, notIn, exists) |

### Error Messages

Validation errors are descriptive:
```
spec.params[0].name is required
spec.preconditions[1].apiCall.method must be one of: GET, POST, PUT, PATCH, DELETE
spec.preconditions[1].apiCall.method "INVALID" is invalid (allowed: GET, POST, PUT, PATCH, DELETE)
spec.resources[0].name "my-resource": must start with lowercase letter and contain only letters, numbers, underscores (no hyphens)
spec.preconditions[0].capture[0]: must have either 'field' or 'expression' set
```
Comment on lines 117 to 124
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add language specifier to code block.

The fenced code block for error message examples should have a language specifier for proper rendering.

Proposed fix
 Validation errors are descriptive:
-```
+```text
 spec.params[0].name is required
 spec.preconditions[1].apiCall.method "INVALID" is invalid (allowed: GET, POST, PUT, PATCH, DELETE)
 spec.resources[0].name "my-resource": must start with lowercase letter and contain only letters, numbers, underscores (no hyphens)
 spec.preconditions[0].capture[0]: must have either 'field' or 'expression' set
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.18.1)</summary>

119-119: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

In @internal/config_loader/README.md around lines 117 - 124, Update the fenced
code block that shows example validation errors in
internal/config_loader/README.md to include a language specifier (e.g., "text")
for proper rendering; locate the block containing the four validation lines
starting with "spec.params[0].name is required" and change the opening fence
from totext so the errors render with the correct formatting.


</details>

<!-- fingerprinting:phantom:poseidon:ocelot -->

<!-- This is an auto-generated comment by CodeRabbit -->


## Types
Expand All @@ -89,8 +134,43 @@ spec.preconditions[1].apiCall.method must be one of: GET, POST, PUT, PATCH, DELE
| `PostConfig` | Post-processing actions |
| `APICall` | HTTP request configuration |
| `Condition` | Field/operator/value condition |
| `CaptureField` | Field capture from API response (see below) |
| `ValueDef` | Dynamic value definition in payload builds (see below) |
| `CaptureField` | Field capture from API response |
| `ValueDef` | Dynamic value definition in payload builds |
| `ValidationErrors` | Collection of validation errors |

### Struct Embedding

The package uses struct embedding to reduce duplication:

```go
// ActionBase - common fields for actions (preconditions, post-actions)
type ActionBase struct {
Name string `yaml:"name" validate:"required"`
APICall *APICall `yaml:"apiCall,omitempty"`
}
Comment on lines +145 to +150
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Document ActionBase.Log in the struct embedding example.
The snippet omits the Log field that post-actions rely on.

📝 Suggested update
 type ActionBase struct {
     Name    string   `yaml:"name" validate:"required"`
     APICall *APICall `yaml:"apiCall,omitempty"`
+    Log     *LogAction `yaml:"log,omitempty"`
 }
📝 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
```go
// ActionBase - common fields for actions (preconditions, post-actions)
type ActionBase struct {
Name string `yaml:"name" validate:"required"`
APICall *APICall `yaml:"apiCall,omitempty"`
}
🤖 Prompt for AI Agents
In `@internal/config_loader/README.md` around lines 145 - 150, The example
ActionBase struct in the README is missing the Log field used by post-actions;
update the ActionBase definition in the documentation to include the Log field
(e.g., Log *LogConfig or similar name/type used elsewhere) so examples and
embedding references to ActionBase.Log compile and make sense—locate the
ActionBase struct in the README and add the Log field with the correct YAML tag
and type consistent with the codebase so post-actions that reference
ActionBase.Log are documented accurately.


// FieldExpressionDef - field OR expression (mutually exclusive)
type FieldExpressionDef struct {
Field string `yaml:"field,omitempty" validate:"required_without=Expression,excluded_with=Expression"`
Expression string `yaml:"expression,omitempty" validate:"required_without=Field,excluded_with=Field"`
}
```

### ValidationErrors

Collect and manage multiple validation errors:

```go
errors := &ValidationErrors{}
errors.Add("path.to.field", "error message")
errors.Extend(otherErrors) // Merge from another ValidationErrors

if errors.HasErrors() {
fmt.Println(errors.First()) // Get first error message
fmt.Println(errors.Count()) // Number of errors
return errors // Implements error interface
}
```

### CaptureField

Expand Down Expand Up @@ -145,7 +225,6 @@ build:

See `types.go` for complete definitions.


## Related

- `internal/criteria` - Evaluates conditions
Expand Down
Loading