diff --git a/pkg/jq/parser.go b/pkg/jq/parser.go index 312d735..c329dfb 100644 --- a/pkg/jq/parser.go +++ b/pkg/jq/parser.go @@ -3,6 +3,7 @@ package jq import ( "fmt" "os" + "reflect" "strings" "sync" @@ -121,3 +122,38 @@ func ParseMapInterface(jqQueries map[string]string, obj interface{}) (map[string return mapInterface, nil } + +func ParseRelations(jqQueries map[string]interface{}, obj interface{}) (map[string]interface{}, error) { + mapInterface := make(map[string]interface{}, len(jqQueries)) + + for key, jqQuery := range jqQueries { + + if reflect.TypeOf(jqQuery).Kind() == reflect.String { + queryRes, _ := ParseMapInterface(map[string]string{key: jqQuery.(string)}, obj) + mapInterface = goutils.MergeMaps(mapInterface, queryRes) + } else if reflect.TypeOf(jqQuery).Kind() == reflect.Map { + for mapKey, mapValue := range jqQuery.(map[string]interface{}) { + queryRes, _ := ParseRelations(map[string]interface{}{mapKey: mapValue}, obj) + for queryKey, queryVal := range queryRes { + if mapInterface[key] == nil { + mapInterface[key] = make(map[string]interface{}) + } + mapInterface[key].(map[string]interface{})[queryKey] = queryVal + } + } + } else if reflect.TypeOf(jqQuery).Kind() == reflect.Slice { + jqArrayValue := reflect.ValueOf(jqQuery) + relations := make([]interface{}, jqArrayValue.Len()) + for i := 0; i < jqArrayValue.Len(); i++ { + relation, err := ParseRelations(map[string]interface{}{key: jqArrayValue.Index(i).Interface()}, obj) + if err != nil { + return nil, err + } + relations[i] = relation[key] + } + mapInterface[key] = relations + } + } + + return mapInterface, nil +} diff --git a/pkg/k8s/controller_test.go b/pkg/k8s/controller_test.go index be345fd..9a9d996 100644 --- a/pkg/k8s/controller_test.go +++ b/pkg/k8s/controller_test.go @@ -3,6 +3,7 @@ package k8s import ( "context" "fmt" + "github.com/port-labs/port-k8s-exporter/pkg/jq" "github.com/stretchr/testify/assert" "reflect" "strings" @@ -215,7 +216,7 @@ func TestCreateDeployment(t *testing.T) { "obj": ".spec.selector", "arr": ".spec.template.spec.containers", }, - Relations: map[string]string{ + Relations: map[string]interface{}{ "k8s-relation": "\"e_AgPMYvq1tAs8TuqM\"", }, }, @@ -226,6 +227,85 @@ func TestCreateDeployment(t *testing.T) { f.runControllerSyncHandler(item, false) } +func TestJqSearchRelation(t *testing.T) { + + mapping := []port.EntityMapping{ + { + Identifier: ".metadata.name", + Blueprint: "\"k8s-export-test-bp\"", + Icon: "\"Microservice\"", + Team: "\"Test\"", + Properties: map[string]string{}, + Relations: map[string]interface{}{ + "k8s-relation": map[string]interface{}{ + "combinator": "\"or\"", + "rules": []interface{}{ + map[string]interface{}{ + "property": "\"$identifier\"", + "operator": "\"=\"", + "value": "\"e_AgPMYvq1tAs8TuqM\"", + }, + }, + }, + }, + }, + } + res, _ := jq.ParseRelations(mapping[0].Relations, nil) + assert.Equal(t, res, map[string]interface{}{ + "k8s-relation": map[string]interface{}{ + "combinator": "or", + "rules": []interface{}{ + map[string]interface{}{ + "property": "$identifier", + "operator": "=", + "value": "e_AgPMYvq1tAs8TuqM", + }, + }, + }, + }) + +} + +func TestCreateDeploymentWithSearchRelation(t *testing.T) { + d := newDeployment() + objects := []runtime.Object{newUnstructured(d)} + item := EventItem{Key: getKey(d, t), ActionType: CreateAction} + resource := newResource("", []port.EntityMapping{ + { + Identifier: ".metadata.name", + Blueprint: "\"k8s-export-test-bp\"", + Icon: "\"Microservice\"", + Team: "\"Test\"", + Properties: map[string]string{ + "text": "\"pod\"", + "num": "1", + "bool": "true", + "obj": ".spec.selector", + "arr": ".spec.template.spec.containers", + }, + Relations: map[string]interface{}{ + "k8s-relation": map[string]interface{}{ + "combinator": "\"or\"", + "rules": []interface{}{ + map[string]interface{}{ + "property": "\"$identifier\"", + "operator": "\"=\"", + "value": "\"e_AgPMYvq1tAs8TuqM\"", + }, + map[string]interface{}{ + "property": "\"$identifier\"", + "operator": "\"=\"", + "value": ".metadata.name", + }, + }, + }, + }, + }, + }) + f := newFixture(t, "", "", "", resource, objects) + f.runControllerSyncHandler(item, false) +} + func TestUpdateDeployment(t *testing.T) { d := newDeployment() objects := []runtime.Object{newUnstructured(d)} @@ -242,7 +322,7 @@ func TestUpdateDeployment(t *testing.T) { "obj": ".spec.selector", "arr": ".spec.template.spec.containers", }, - Relations: map[string]string{ + Relations: map[string]interface{}{ "k8s-relation": "\"e_AgPMYvq1tAs8TuqM\"", }, }, @@ -379,7 +459,7 @@ func TestUpdateHandlerWithIndividualPropertyChanges(t *testing.T) { "generateName": ".metadata.generateName", "creationTimestamp": ".metadata.creationTimestamp", }, - Relations: map[string]string{ + Relations: map[string]interface{}{ "k8s-relation": "\"e_AgPMYvq1tAs8TuqM\"", }, }, @@ -392,7 +472,7 @@ func TestUpdateHandlerWithIndividualPropertyChanges(t *testing.T) { Icon: "\"Microservice\"", Team: "\"Test\"", Properties: map[string]string{}, - Relations: map[string]string{}, + Relations: map[string]interface{}{}, }, { Identifier: ".metadata.name", @@ -405,7 +485,7 @@ func TestUpdateHandlerWithIndividualPropertyChanges(t *testing.T) { "generateName": ".metadata.generateName", "creationTimestamp": ".metadata.creationTimestamp", }, - Relations: map[string]string{ + Relations: map[string]interface{}{ "k8s-relation": "\"e_AgPMYvq1tAs8TuqM\"", }, }, diff --git a/pkg/port/mapping/entity.go b/pkg/port/mapping/entity.go index e0e951c..1cb4d70 100644 --- a/pkg/port/mapping/entity.go +++ b/pkg/port/mapping/entity.go @@ -38,7 +38,7 @@ func NewEntity(obj interface{}, mapping port.EntityMapping) (*port.Entity, error if err != nil { return &port.Entity{}, err } - entity.Relations, err = jq.ParseMapInterface(mapping.Relations, obj) + entity.Relations, err = jq.ParseRelations(mapping.Relations, obj) if err != nil { return &port.Entity{}, err } diff --git a/pkg/port/models.go b/pkg/port/models.go index 4d51c2c..ab9bc4e 100644 --- a/pkg/port/models.go +++ b/pkg/port/models.go @@ -221,13 +221,13 @@ type ResponseBody struct { } type EntityMapping struct { - Identifier string `json:"identifier" yaml:"identifier"` - Title string `json:"title" yaml:"title"` - Blueprint string `json:"blueprint" yaml:"blueprint"` - Icon string `json:"icon,omitempty" yaml:"icon,omitempty"` - Team string `json:"team,omitempty" yaml:"team,omitempty"` - Properties map[string]string `json:"properties,omitempty" yaml:"properties,omitempty"` - Relations map[string]string `json:"relations,omitempty" yaml:"relations,omitempty"` + Identifier string `json:"identifier" yaml:"identifier"` + Title string `json:"title" yaml:"title"` + Blueprint string `json:"blueprint" yaml:"blueprint"` + Icon string `json:"icon,omitempty" yaml:"icon,omitempty"` + Team string `json:"team,omitempty" yaml:"team,omitempty"` + Properties map[string]string `json:"properties,omitempty" yaml:"properties,omitempty"` + Relations map[string]interface{} `json:"relations,omitempty" yaml:"relations,omitempty"` } type EntityMappings struct {