Skip to content

Commit

Permalink
feat: select related components via config (#1084)
Browse files Browse the repository at this point in the history
* chore: select related components via config

* chore: make component config traverse part of search

* chore: address review comments

* chore: add more options for resource selector search

* chore: avoid using types in component_config_traverse
  • Loading branch information
yashmehrotra authored Sep 25, 2024
1 parent ed6824a commit cd63649
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 14 deletions.
90 changes: 77 additions & 13 deletions query/resource_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package query

import (
"fmt"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -130,6 +131,28 @@ func SearchResources(ctx context.Context, req SearchResourcesRequest) (*SearchRe
}

func SetResourceSelectorClause(ctx context.Context, resourceSelector types.ResourceSelector, query *gorm.DB, table string, allowedColumnsAsFields []string) (*gorm.DB, error) {

// We call setSearchQueryParams as it sets those params that
// might later be used by the query
if resourceSelector.Search != "" {
if strings.Contains(resourceSelector.Search, "=") {
setSearchQueryParams(&resourceSelector)
} else {
var prefixQueries []*gorm.DB
if resourceSelector.Name == "" {
prefixQueries = append(prefixQueries, ctx.DB().Where("name ILIKE ?", resourceSelector.Search+"%"))
}
if resourceSelector.TagSelector == "" && table == "config_items" {
prefixQueries = append(prefixQueries, ctx.DB().Where("EXISTS (SELECT 1 FROM jsonb_each_text(tags) WHERE value ILIKE ?)", resourceSelector.Search+"%"))
}
if resourceSelector.LabelSelector == "" {
prefixQueries = append(prefixQueries, ctx.DB().Where("EXISTS (SELECT 1 FROM jsonb_each_text(labels) WHERE value ILIKE ?)", resourceSelector.Search+"%"))
}

query = OrQueries(query, prefixQueries...)
}
}

if !resourceSelector.IncludeDeleted {
query = query.Where("deleted_at IS NULL")
}
Expand Down Expand Up @@ -224,24 +247,61 @@ func SetResourceSelectorClause(ctx context.Context, resourceSelector types.Resou
}
}

if resourceSelector.Search != "" {
var prefixQueries []*gorm.DB
if resourceSelector.Name == "" {
prefixQueries = append(prefixQueries, ctx.DB().Where("name ILIKE ?", resourceSelector.Search+"%"))
}
if resourceSelector.TagSelector == "" && table == "config_items" {
prefixQueries = append(prefixQueries, ctx.DB().Where("EXISTS (SELECT 1 FROM jsonb_each_text(tags) WHERE value ILIKE ?)", resourceSelector.Search+"%"))
if resourceSelector.Functions.ComponentConfigTraversal != nil {
args := resourceSelector.Functions.ComponentConfigTraversal
if table == "components" {
query = query.Where("id IN (SELECT id from lookup_component_config_id_related_components(?, ?))", args.ComponentID, args.Direction)
}
if resourceSelector.LabelSelector == "" {
prefixQueries = append(prefixQueries, ctx.DB().Where("EXISTS (SELECT 1 FROM jsonb_each_text(labels) WHERE value ILIKE ?)", resourceSelector.Search+"%"))
}

query = OrQueries(query, prefixQueries...)
}

return query, nil
}

func setSearchQueryParams(rs *types.ResourceSelector) {
if rs.Search == "" {
return
}

queries := strings.Split(rs.Search, " ")
for _, q := range queries {
items := strings.Split(q, "=")
if len(items) != 2 {
continue
}

switch items[0] {
case "component_config_traverse":
// search: component_config_traverse=72143d48-da4a-477f-bac1-1e9decf188a6,outgoing
// Args should be componentID, direction and types (compID,direction)
args := strings.Split(items[1], ",")
if len(args) == 2 {
rs.Functions.ComponentConfigTraversal = &types.ComponentConfigTraversalArgs{
ComponentID: args[0],
Direction: args[1],
}
}
case "id":
rs.ID = items[1]
case "name":
rs.Name = items[1]
case "namespace":
rs.Namespace = items[1]
case "type":
rs.Types = append(rs.Types, strings.Split(items[1], ",")...)
case "status":
rs.Statuses = append(rs.Statuses, strings.Split(items[1], ",")...)
case "limit":
l, _ := strconv.Atoi(items[1])
rs.Limit = l
case "scope":
rs.Scope = items[1]
default:
// key=val
rs.LabelSelector += strings.Join([]string{rs.LabelSelector, q}, ",")
}
}
}

// queryResourceSelector runs the given resourceSelector and returns the resource ids
func queryResourceSelector(ctx context.Context, limit int, resourceSelector types.ResourceSelector, table string, allowedColumnsAsFields []string) ([]uuid.UUID, error) {
if resourceSelector.IsEmpty() {
Expand All @@ -261,7 +321,11 @@ func queryResourceSelector(ctx context.Context, limit int, resourceSelector type
}

query := ctx.DB().Select("id").Table(table)
if limit > 0 {

// Resource selector's limit gets higher priority
if resourceSelector.Limit > 0 {
query = query.Limit(resourceSelector.Limit)
} else if limit > 0 {
query = query.Limit(limit)
}

Expand Down
3 changes: 2 additions & 1 deletion types/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/flanksource/commons/collections"
"github.com/flanksource/gomplate/v3"
"github.com/lib/pq"
"github.com/samber/lo"
"gorm.io/gorm"
)
Expand Down Expand Up @@ -51,7 +52,7 @@ func asMap(t any, removeFields ...string) map[string]any {
return m
}

type Items []string
type Items pq.StringArray

func (items Items) String() string {
return strings.Join(items, ",")
Expand Down
16 changes: 16 additions & 0 deletions types/resource_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ import (
"k8s.io/apimachinery/pkg/labels"
)

type ComponentConfigTraversalArgs struct {
ComponentID string `yaml:"component_id,omitempty" json:"component_id,omitempty"`
Direction string `yaml:"direction,omitempty" json:"direction,omitempty"`
}

type Functions struct {
// It uses the config_id linked to the componentID to lookup up all the config relations and returns
// a list of componentIDs that are linked to the found configIDs
ComponentConfigTraversal *ComponentConfigTraversalArgs `yaml:"component_config_traversal,omitempty" json:"component_config_traversal,omitempty"`
}

// +kubebuilder:object:generate=true
type ResourceSelector struct {
// Agent can be the agent id or the name of the agent.
Expand All @@ -37,6 +48,11 @@ type ResourceSelector struct {
// Search query that applies to the resource name, tag & labels.
Search string `yaml:"search,omitempty" json:"search,omitempty"`

// Use custom functions for specific selections
Functions Functions `yaml:"-" json:"-"`

Limit int `yaml:"limit,omitempty" json:"limit,omitempty"`

IncludeDeleted bool `yaml:"includeDeleted,omitempty" json:"includeDeleted,omitempty"`

ID string `yaml:"id,omitempty" json:"id,omitempty"`
Expand Down
22 changes: 22 additions & 0 deletions views/005_component_views.sql
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,25 @@ $$ language plpgsql;
CREATE OR REPLACE VIEW component_types AS
SELECT distinct on (type) type
FROM components ORDER BY type asc;

CREATE OR REPLACE FUNCTION lookup_component_config_id_related_components (
component_id TEXT,
type_filter TEXT DEFAULT 'outgoing'
)
RETURNS TABLE (id UUID) AS $$
BEGIN
RETURN QUERY
WITH related_config_ids AS (
SELECT *
FROM related_configs_recursive(
(SELECT config_id FROM components WHERE components.id = $1::UUID),
$2,
FALSE,
10
)
)
SELECT components.id
FROM components
WHERE config_id IN (SELECT related_config_ids.id FROM related_config_ids);
END;
$$ LANGUAGE plpgsql;

0 comments on commit cd63649

Please sign in to comment.