Skip to content
Open
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 @@ -52,12 +52,16 @@ 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-playground/validator/v10 v10.30.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 @@ -71,6 +75,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 @@ -121,13 +126,13 @@ require (
go.opentelemetry.io/otel/trace v1.36.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
18 changes: 18 additions & 0 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,12 @@ 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/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 @@ -180,6 +188,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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,6 +354,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
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,6 +375,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
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,8 +399,12 @@ 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/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.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/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
```

## 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"`
}

// 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