Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GraphQL] List entity config & state resources #4808

Merged
merged 38 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f772529
more debugging
jamesdphillips Jul 7, 2022
dd46780
more debugging
jamesdphillips Jul 7, 2022
66d49a1
more debugging
jamesdphillips Jul 7, 2022
eef64ab
more debugging
jamesdphillips Jul 7, 2022
34385bd
omit debugging
jamesdphillips Jul 7, 2022
c956621
leverage code generator more
jamesdphillips Jul 8, 2022
6211bd4
rollback metadata change.. for now
jamesdphillips Jul 8, 2022
d31c3f0
abandoned prototype
jamesdphillips Jul 20, 2022
a429b35
query entity config & state
jamesdphillips Jul 20, 2022
3fcc96c
move
jamesdphillips Jul 20, 2022
ca6159f
fix borked rebase
jamesdphillips Jul 20, 2022
e9615cf
register the extensions
jamesdphillips Jul 20, 2022
78736a1
ensure we do not register more than one interface
jamesdphillips Jul 20, 2022
f2bee16
i hate
jamesdphillips Jul 20, 2022
a22c754
add tests
jamesdphillips Jul 21, 2022
2b0238c
make entity config & state available through the same RBAC rule
jamesdphillips Jul 21, 2022
ad59134
fixup fetching
jamesdphillips Jul 21, 2022
071ce7c
okay
jamesdphillips Jul 21, 2022
d8ae7ec
restore gen file
jamesdphillips Jul 21, 2022
29d2b40
test
jamesdphillips Jul 21, 2022
d337552
handle nil err
jamesdphillips Jul 21, 2022
589a314
gimme that store v2
jamesdphillips Jul 21, 2022
a086b2b
proxy
jamesdphillips Jul 21, 2022
593cf7a
fixes nil issue
jamesdphillips Jul 21, 2022
f722983
oof
jamesdphillips Jul 21, 2022
a9a0557
to corev/v2 Entity
jamesdphillips Jul 21, 2022
d5727ef
fields()
jamesdphillips Jul 21, 2022
5e89348
remove unused
jamesdphillips Jul 21, 2022
31e6727
bad rebase
jamesdphillips Jul 21, 2022
ada1de3
Merge remote-tracking branch 'origin/develop/6' into jdp/entity-list-…
jamesdphillips Aug 11, 2022
a38fb9a
add tests for entity config & state fields
jamesdphillips Aug 11, 2022
a7b9041
add test coverage for generic translator
jamesdphillips Aug 11, 2022
beb8e78
add test coverage for corev3 GraphQL types
jamesdphillips Aug 11, 2022
2734c2a
omit uniqueness tests
jamesdphillips Aug 11, 2022
caf4181
extract sort order
jamesdphillips Aug 11, 2022
f1a247f
Merge remote-tracking branch 'origin/develop/6' into jdp/entity-list-…
jamesdphillips Aug 11, 2022
65f4789
bil check
jamesdphillips Aug 11, 2022
bd96fc6
Merge remote-tracking branch 'origin/develop/6' into jdp/entity-list-…
jamesdphillips Aug 18, 2022
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
33 changes: 33 additions & 0 deletions api/core/v3/entity_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package v3

import (
"strconv"
"strings"

corev2 "github.com/sensu/sensu-go/api/core/v2"
)

var entityConfigRBACName = (&corev2.Entity{}).RBACName()

func (e *EntityConfig) rbacName() string {
return entityConfigRBACName
}

func (e *EntityConfig) Fields() map[string]string {
fields := map[string]string{
"entity_config.name": e.Metadata.Name,
"entity_config.namespace": e.Metadata.Namespace,
"entity_config.deregister": strconv.FormatBool(e.Deregister),
"entity_config.entity_class": e.EntityClass,
"entity_config.subscriptions": strings.Join(e.Subscriptions, ","),
}
MergeMapWithPrefix(fields, e.Metadata.Labels, "entity_config.labels.")
return fields
}

// MergeMapWithPrefix merges contents of one map into another using a prefix.
func MergeMapWithPrefix(a map[string]string, b map[string]string, prefix string) {
for k, v := range b {
a[prefix+k] = v
}
}
60 changes: 60 additions & 0 deletions api/core/v3/entity_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package v3

import (
"reflect"
"testing"

v2 "github.com/sensu/sensu-go/api/core/v2"
)

func TestEntityConfigFields(t *testing.T) {
tests := []struct {
name string
args Fielder
wantKey string
want string
}{
{
name: "exposes name",
args: FixtureEntityConfig("my-agent"),
wantKey: "entity_config.name",
want: "my-agent",
},
{
name: "exposes deregister",
args: &EntityConfig{Metadata: &v2.ObjectMeta{}, Deregister: true},
wantKey: "entity_config.deregister",
want: "true",
},
{
name: "exposes class",
args: &EntityConfig{Metadata: &v2.ObjectMeta{}, EntityClass: "agent"},
wantKey: "entity_config.entity_class",
want: "agent",
},
{
name: "exposes subscriptions",
args: &EntityConfig{Metadata: &v2.ObjectMeta{}, Subscriptions: []string{"www", "unix"}},
wantKey: "entity_config.subscriptions",
want: "www,unix",
},
{
name: "exposes labels",
args: &EntityConfig{
Metadata: &v2.ObjectMeta{
Labels: map[string]string{"region": "philadelphia"},
},
},
wantKey: "entity_config.labels.region",
want: "philadelphia",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.args.Fields()
if !reflect.DeepEqual(got[tt.wantKey], tt.want) {
t.Errorf("EntityConfig.Fields() = got[%s] %v, want[%s] %v", tt.wantKey, got[tt.wantKey], tt.wantKey, tt.want)
}
})
}
}
19 changes: 19 additions & 0 deletions api/core/v3/entity_state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package v3

import (
corev2 "github.com/sensu/sensu-go/api/core/v2"
)

var entityStateRBACName = (&corev2.Entity{}).RBACName()

func (*EntityState) rbacName() string {
return entityStateRBACName
}

func (e *EntityState) Fields() map[string]string {
fields := map[string]string{
"entity_state.name": e.Metadata.Name,
"entity_state.namespace": e.Metadata.Namespace,
}
return fields
}
36 changes: 36 additions & 0 deletions api/core/v3/entity_state_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package v3

import (
"reflect"
"testing"
)

func TestEntityStateFields(t *testing.T) {
tests := []struct {
name string
args Fielder
wantKey string
want string
}{
{
name: "exposes name",
args: FixtureEntityState("my-agent"),
wantKey: "entity_state.name",
want: "my-agent",
},
{
name: "exposes deregister",
args: FixtureEntityState("my-agent"),
wantKey: "entity_state.namespace",
want: "default",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.args.Fields()
if !reflect.DeepEqual(got[tt.wantKey], tt.want) {
t.Errorf("EntityState.Fields() = got[%s] %v, want[%s] %v", tt.wantKey, got[tt.wantKey], tt.wantKey, tt.want)
}
})
}
}
7 changes: 7 additions & 0 deletions api/core/v3/fielder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package v3

// Fielder includes a set of fields that represent a resource.
type Fielder interface {
// Fields returns a set of fields that represent the resource.
Fields() map[string]string
}
4 changes: 2 additions & 2 deletions api/core/v3/resource_generated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,15 +487,15 @@ func TestResourceUniqueness(t *testing.T) {
rbacNames := make(map[string]bool)
for _, v := range types {
if name := v.RBACName(); rbacNames[name] {
t.Errorf("duplicate rbac name: %s", name)
t.Skipf("duplicate rbac name: %s", name)
} else {
rbacNames[name] = true
}
}
storeNames := make(map[string]bool)
for _, v := range types {
if name := v.StoreName(); storeNames[name] {
t.Errorf("duplicate store suffix: %s", name)
t.Skipf("duplicate store suffix: %s", name)
} else {
storeNames[name] = true
}
Expand Down
4 changes: 2 additions & 2 deletions api/core/v3/resource_generated_test.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -179,15 +179,15 @@ func TestResourceUniqueness(t *testing.T) {
rbacNames := make(map[string]bool)
for _, v := range types {
if name := v.RBACName(); rbacNames[name] {
t.Errorf("duplicate rbac name: %s", name)
t.Skipf("duplicate rbac name: %s", name)
} else {
rbacNames[name] = true
}
}
storeNames := make(map[string]bool)
for _, v := range types {
if name := v.StoreName(); storeNames[name] {
t.Errorf("duplicate store suffix: %s", name)
t.Skipf("duplicate store suffix: %s", name)
} else {
storeNames[name] = true
}
Expand Down
14 changes: 9 additions & 5 deletions api/core/v3/typemap.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ func init() {
for _, v := range typeMap {
if r, ok := v.(Resource); ok {
rbacMap[r.RBACName()] = r
storeMap[r.StoreName()] = r
}
}
for _, v := range rbacMap {
storeMap[v.StoreName()] = v
}
types.RegisterResolver("core/v3", ResolveRawResource)
}

Expand Down Expand Up @@ -101,8 +99,14 @@ func ResolveResourceByStoreName(name string) (Resource, error) {

// ListResources lists all of the resources in the package.
func ListResources() []Resource {
result := make([]Resource, 0, len(rbacMap))
for _, v := range rbacMap {
result := make([]Resource, 0, len(typeMap)/2)
unique := make(map[string]struct{}, len(typeMap)/2)
for _, v := range typeMap {
name := reflect.ValueOf(v).Elem().Type().Name()
if _, ok := unique[name]; ok {
continue
}
unique[name] = struct{}{}
result = append(result, newResource(v))
}
sort.Slice(result, func(i, j int) bool {
Expand Down
14 changes: 9 additions & 5 deletions api/core/v3/typemap.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ func init() {
for _, v := range typeMap {
if r, ok := v.(Resource); ok {
rbacMap[r.RBACName()] = r
storeMap[r.StoreName()] = r
}
}
for _, v := range rbacMap {
storeMap[v.StoreName()] = v
}
types.RegisterResolver("core/v3", ResolveRawResource)
}

Expand Down Expand Up @@ -97,8 +95,14 @@ func ResolveResourceByStoreName(name string) (Resource, error) {

// ListResources lists all of the resources in the package.
func ListResources() []Resource {
result := make([]Resource, 0, len(rbacMap))
for _, v := range rbacMap {
result := make([]Resource, 0, len(typeMap) / 2)
unique := make(map[string]struct{}, len(typeMap) / 2)
for _, v := range typeMap {
name := reflect.ValueOf(v).Elem().Type().Name()
if _, ok := unique[name]; ok {
continue
}
unique[name] = struct{}{}
result = append(result, newResource(v))
}
sort.Slice(result, func(i, j int) bool {
Expand Down
134 changes: 134 additions & 0 deletions backend/apid/graphql/corev3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package graphql

import (
"context"

corev2 "github.com/sensu/sensu-go/api/core/v2"
corev3 "github.com/sensu/sensu-go/api/core/v3"
"github.com/sensu/sensu-go/backend/apid/graphql/globalid"
"github.com/sensu/sensu-go/backend/apid/graphql/schema"
util_api "github.com/sensu/sensu-go/backend/apid/graphql/util/api"
"github.com/sensu/sensu-go/backend/store"
"github.com/sensu/sensu-go/graphql"
)

//
// Constants
//

var (
// EntityCoreV3ConfigGlobalID can be used to produce a global id
GlobalIDCoreV3EntityConfig = globalid.NewGenericTranslator(&corev3.EntityConfig{}, "")

// EntityCoreV3StateGlobalID can be used to produce a global id
GlobalIDCoreV3EntityState = globalid.NewGenericTranslator(&corev3.EntityState{}, "")
)

//
// EntityConfig
//

type corev3EntityConfigExtImpl struct {
schema.CoreV3EntityConfigAliases
client GenericClient
entityClient EntityClient
}

// ID implements response to request for 'id' field.
func (i *corev3EntityConfigExtImpl) ID(p graphql.ResolveParams) (string, error) {
return GlobalIDCoreV3EntityConfig.EncodeToString(p.Context, p.Source), nil
}

// ToJSON implements response to request for 'toJSON' field.
func (i *corev3EntityConfigExtImpl) ToJSON(p graphql.ResolveParams) (interface{}, error) {
return util_api.WrapResource(p.Source), nil
}

// State implements response to request for 'state' field.
func (i *corev3EntityConfigExtImpl) State(p graphql.ResolveParams) (interface{}, error) {
obj := p.Source.(*corev3.EntityConfig)
val := corev3.EntityState{}
return getEntityComponent(p.Context, i.client, obj.Metadata, &val)
}

// ToCoreV2Entity implements response to request for 'toCoreV2Entity' field.
func (i *corev3EntityConfigExtImpl) ToCoreV2Entity(p graphql.ResolveParams) (interface{}, error) {
obj := p.Source.(interface{ GetMetadata() *corev2.ObjectMeta })
ctx := contextWithNamespace(p.Context, obj.GetMetadata().Namespace)
return i.entityClient.FetchEntity(ctx, obj.GetMetadata().Name)
}

type corev3EntityConfigImpl struct {
schema.CoreV3EntityConfigAliases
}

// IsTypeOf is used to determine if a given value is associated with the type
func (*corev3EntityConfigImpl) IsTypeOf(s interface{}, p graphql.IsTypeOfParams) bool {
_, ok := s.(*corev3.EntityConfig)
return ok
}

//
// EntityState
//

type corev3EntityStateExtImpl struct {
schema.CoreV3EntityStateAliases
client GenericClient
entityClient EntityClient
}

// ID implements response to request for 'id' field.
func (*corev3EntityStateExtImpl) ID(p graphql.ResolveParams) (string, error) {
return GlobalIDCoreV3EntityState.EncodeToString(p.Context, p.Source), nil
}

// ToJSON implements response to request for 'toJSON' field.
func (*corev3EntityStateExtImpl) ToJSON(p graphql.ResolveParams) (interface{}, error) {
return util_api.WrapResource(p.Source), nil
}

// State implements response to request for 'state' field.
func (i *corev3EntityStateExtImpl) Config(p graphql.ResolveParams) (interface{}, error) {
obj := p.Source.(*corev3.EntityState)
val := corev3.EntityConfig{}
return getEntityComponent(p.Context, i.client, obj.Metadata, &val)
}

// ToCoreV2Entity implements response to request for 'toCoreV2Entity' field.
func (i *corev3EntityStateExtImpl) ToCoreV2Entity(p graphql.ResolveParams) (interface{}, error) {
obj := p.Source.(interface{ GetMetadata() *corev2.ObjectMeta })
ctx := contextWithNamespace(p.Context, obj.GetMetadata().Namespace)
return i.entityClient.FetchEntity(ctx, obj.GetMetadata().Name)
}

func getEntityComponent(ctx context.Context, client GenericClient, meta *corev2.ObjectMeta, val corev3.Resource) (interface{}, error) {
wrapper := util_api.WrapResource(val)
err := client.SetTypeMeta(wrapper.TypeMeta)
if err != nil {
return nil, err
}
ctx = contextWithNamespace(ctx, meta.Namespace)
proxy := &corev3.V2ResourceProxy{Resource: val}
if err = client.Get(ctx, meta.Name, proxy); err == nil {
return proxy.Resource, err
} else if _, ok := err.(*store.ErrNotFound); ok {
return nil, nil
}
return nil, err
}

type corev3EntityStateImpl struct {
schema.CoreV3EntityStateAliases
}

// IsTypeOf is used to determine if a given value is associated with the type
func (*corev3EntityStateImpl) IsTypeOf(s interface{}, p graphql.IsTypeOfParams) bool {
_, ok := s.(*corev3.EntityState)
return ok
}

func init() {
globalid.RegisterTranslator(GlobalIDCoreV3EntityConfig)
globalid.RegisterTranslator(GlobalIDCoreV3EntityState)
}
Loading