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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@ hyperfleet-adapter/
│ └── logger/ # Structured logging with context support
├── internal/
│ ├── broker_consumer/ # Message broker consumer implementations
│ ├── config-loader/ # Configuration loading logic
│ ├── criteria/ # Precondition evaluation
│ ├── config_loader/ # Configuration loading logic
│ ├── criteria/ # Precondition and CEL evaluation
│ ├── executor/ # Event execution engine
│ ├── hyperfleet_api/ # HyperFleet API client
│ └── k8s-objects/ # Kubernetes object management
│ └── k8s_client/ # Kubernetes client wrapper
├── test/ # Integration tests
├── charts/ # Helm chart for Kubernetes deployment
├── Dockerfile # Multi-stage Docker build
Expand Down
6 changes: 6 additions & 0 deletions configs/adapter-config-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,9 @@ spec:
method: "POST"
url: "{{ .hyperfleetApiBaseUrl }}/api/{{ .hyperfleetApiVersion }}/clusters/{{ .clusterId }}/status"
body: "{{ .clusterStatusPayload }}"
timeout: 30s
retryAttempts: 3
retryBackoff: "exponential"
headers:
- name: "Content-Type"
value: "application/json"
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/golang/glog v1.2.5
github.com/google/cel-go v0.26.1
github.com/google/uuid v1.6.0
github.com/mitchellh/copystructure v1.2.0
github.com/openshift-hyperfleet/hyperfleet-broker v0.0.1
github.com/stretchr/testify v1.11.1
github.com/testcontainers/testcontainers-go v0.40.0
Expand Down Expand Up @@ -71,6 +72,7 @@ require (
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
Expand All @@ -83,6 +85,8 @@ require (
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/onsi/ginkgo/v2 v2.25.1 // indirect
github.com/onsi/gomega v1.38.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
Expand Down
20 changes: 14 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ThreeDotsLabs/watermill v1.5.1 h1:t5xMivyf9tpmU3iozPqyrCZXHvoV1XQDfihas4sV0fY=
Expand Down Expand Up @@ -143,8 +145,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
Expand Down Expand Up @@ -185,6 +187,10 @@ github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8S
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
Expand Down Expand Up @@ -213,10 +219,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/onsi/ginkgo/v2 v2.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY=
github.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
Expand Down Expand Up @@ -318,6 +324,8 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
Expand Down
2 changes: 1 addition & 1 deletion internal/config_loader/accessors.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func BuiltinVariables() []string {
// - Built-in variables (metadata, now, date)
// - Parameters from spec.params
// - Captured variables from preconditions
// - Post params
// - Post payloads
// - Resource aliases (resources.<name>)
func (c *AdapterConfig) GetDefinedVariables() map[string]bool {
vars := make(map[string]bool)
Expand Down
16 changes: 12 additions & 4 deletions internal/config_loader/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ type KubernetesConfig struct {
APIVersion string `yaml:"apiVersion"`
}

// Parameter represents a static parameter extraction configuration.
// Parameters are inputs extracted from external sources (event data, env vars).
// Parameter represents a parameter extraction configuration.
// Parameters are extracted from external sources (event data, env vars) using Source.
type Parameter struct {
Name string `yaml:"name"`
Source string `yaml:"source,omitempty"`
Expand Down Expand Up @@ -101,6 +101,7 @@ type Precondition struct {
Capture []CaptureField `yaml:"capture,omitempty"`
Conditions []Condition `yaml:"conditions,omitempty"`
Expression string `yaml:"expression,omitempty"`
Log *LogAction `yaml:"log,omitempty"`
}

// APICall represents an API call configuration
Expand Down Expand Up @@ -196,8 +197,15 @@ type PostConfig struct {

// PostAction represents a post-processing action
type PostAction struct {
Name string `yaml:"name"`
APICall *APICall `yaml:"apiCall,omitempty"`
Name string `yaml:"name"`
APICall *APICall `yaml:"apiCall,omitempty"`
Log *LogAction `yaml:"log,omitempty"`
}

// LogAction represents a logging action that can be configured in the adapter config
type LogAction struct {
Message string `yaml:"message"`
Level string `yaml:"level,omitempty"` // debug, info, warning, error (default: info)
}

// ManifestRef represents a manifest reference
Expand Down
21 changes: 21 additions & 0 deletions internal/config_loader/validator_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"

"gopkg.in/yaml.v3"
)

// validResourceNameRegex validates resource names for CEL compatibility.
// Allows snake_case (my_resource) and camelCase (myResource).
// Must start with lowercase letter, can contain letters, numbers, underscores.
// Hyphens (kebab-case) are NOT allowed as they conflict with CEL's minus operator.
var validResourceNameRegex = regexp.MustCompile(`^[a-z][a-zA-Z0-9_]*$`)

// -----------------------------------------------------------------------------
// SchemaValidator
// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -142,13 +149,27 @@ func (v *SchemaValidator) validatePreconditions() error {
}

func (v *SchemaValidator) validateResources() error {
seen := make(map[string]bool)

for i, resource := range v.config.Spec.Resources {
path := fmt.Sprintf("%s.%s[%d]", FieldSpec, FieldResources, i)

if resource.Name == "" {
return fmt.Errorf("%s.%s is required", path, FieldName)
}

// Validate resource name format for CEL compatibility
// Allows snake_case and camelCase, but NOT kebab-case (hyphens conflict with CEL minus operator)
if !validResourceNameRegex.MatchString(resource.Name) {
return fmt.Errorf("%s.%s %q: must start with lowercase letter and contain only letters, numbers, underscores (no hyphens)", path, FieldName, resource.Name)
}

// Check for duplicate resource names
if seen[resource.Name] {
return fmt.Errorf("%s.%s %q: duplicate resource name", path, FieldName, resource.Name)
}
seen[resource.Name] = true

if resource.Manifest == nil {
return fmt.Errorf("%s (%s): %s is required", path, resource.Name, FieldManifest)
}
Expand Down
3 changes: 1 addition & 2 deletions internal/config_loader/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ package config_loader
import (
"testing"

"github.com/openshift-hyperfleet/hyperfleet-adapter/internal/criteria"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/openshift-hyperfleet/hyperfleet-adapter/internal/criteria"
)

func TestValidateConditionOperators(t *testing.T) {
Expand Down
14 changes: 7 additions & 7 deletions internal/criteria/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ ctx.Set("provider", "aws")
ctx.Set("nodeCount", 5)

// Create evaluator
evaluator := criteria.NewEvaluator(ctx)
evaluator := criteria.NewEvaluator(ctx, log)

// Evaluate a single condition
result, err := evaluator.EvaluateCondition(
Expand Down Expand Up @@ -132,7 +132,7 @@ ctx.Set("clusterPhase", "Ready")
ctx.Set("cloudProvider", "aws")
ctx.Set("vpcId", "vpc-12345")

evaluator := criteria.NewEvaluator(ctx)
evaluator := criteria.NewEvaluator(ctx, log)

// Validate cluster is in correct phase
phaseValid, _ := evaluator.EvaluateCondition(
Expand Down Expand Up @@ -179,7 +179,7 @@ ctx.Set("resources", map[string]interface{}{
},
})

evaluator := criteria.NewEvaluator(ctx)
evaluator := criteria.NewEvaluator(ctx, log)

// Check namespace is active
nsActive, _ := evaluator.EvaluateCondition(
Expand All @@ -204,7 +204,7 @@ if nsActive && allReady {

```go
ctx := criteria.NewEvaluationContext()
evaluator := criteria.NewEvaluator(ctx)
evaluator := criteria.NewEvaluator(ctx, log)

// String contains
ctx.Set("message", "Deployment ready and healthy")
Expand Down Expand Up @@ -233,7 +233,7 @@ ctx.Set("nodeCount", 5)
ctx.Set("minNodes", 1)
ctx.Set("maxNodes", 10)

evaluator := criteria.NewEvaluator(ctx)
evaluator := criteria.NewEvaluator(ctx, log)

// Check if within range
aboveMin, _ := evaluator.EvaluateCondition(
Expand Down Expand Up @@ -276,7 +276,7 @@ ctx.Set("cloudProvider", "aws")
ctx.Set("vpcId", "vpc-12345")

// Evaluate precondition conditions
evaluator := criteria.NewEvaluator(ctx)
evaluator := criteria.NewEvaluator(ctx, log)
conditions := make([]criteria.ConditionDef, len(precond.Conditions))
for i, cond := range precond.Conditions {
conditions[i] = criteria.ConditionDef{
Expand Down Expand Up @@ -306,7 +306,7 @@ The package provides descriptive error messages:
ctx := criteria.NewEvaluationContext()
ctx.Set("count", "not a number")

evaluator := criteria.NewEvaluator(ctx)
evaluator := criteria.NewEvaluator(ctx, log)
result, err := evaluator.EvaluateCondition(
"count",
criteria.OperatorGreaterThan,
Expand Down
Loading