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

Add Director client functions to set runtime connection status #18623

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
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
1 change: 1 addition & 0 deletions components/compass-runtime-agent/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/kyma-project/kyma/components/compass-runtime-agent
go 1.21

require (
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0
github.com/google/uuid v1.5.0
github.com/gorilla/mux v1.8.1
github.com/kofalt/go-memoize v0.0.0-20200917044458-9b55a8d73e1c
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,15 @@ type RuntimeURLsConfig struct {
ConsoleURL string `envconfig:"default=https://console.kyma.local"`
}

type GQLResponse[T graphql.RuntimeExt | graphql.Runtime] struct {
Result *T
}

//go:generate mockery --name=DirectorClient
type DirectorClient interface {
FetchConfiguration(ctx context.Context) ([]kymamodel.Application, graphql.Labels, error)
SetURLsLabels(ctx context.Context, urlsCfg RuntimeURLsConfig, actualLabels graphql.Labels) (graphql.Labels, error)
SetRuntimeStatusCondition(ctx context.Context, statusCondition graphql.RuntimeStatusCondition) error
}

func NewConfigurationClient(gqlClient gql.Client, runtimeConfig config.RuntimeConfig) DirectorClient {
Expand All @@ -41,6 +46,7 @@ type directorClient struct {
gqlClient gql.Client
queryProvider queryProvider
runtimeConfig config.RuntimeConfig
graphqlizer Graphqlizer
}

func (cc *directorClient) FetchConfiguration(ctx context.Context) ([]kymamodel.Application, graphql.Labels, error) {
Expand Down Expand Up @@ -110,3 +116,70 @@ func (cc *directorClient) setURLLabel(ctx context.Context, key, value string) (*

return response.Result, nil
}

func (cc *directorClient) SetRuntimeStatusCondition(ctx context.Context, statusCondition graphql.RuntimeStatusCondition) error {
// TODO: Set StatusCondition without getting the Runtime
// It'll be possible after this issue implementation:
// - https://github.com/kyma-incubator/compass/issues/1186
runtime, err := cc.getRuntime(ctx)
if err != nil {
return err
}
runtimeInput := &graphql.RuntimeInput{
Name: runtime.Name,
Description: runtime.Description,
StatusCondition: &statusCondition,
Labels: runtime.Labels,
}
err = cc.updateRuntime(ctx, cc.runtimeConfig.RuntimeId, runtimeInput)
if err != nil {
return err
}
return nil
}

func (cc *directorClient) getRuntime(ctx context.Context) (graphql.RuntimeExt, error) {

runtimeQuery := cc.queryProvider.getRuntimeQuery(cc.runtimeConfig.RuntimeId)

req := gcli.NewRequest(runtimeQuery)
req.Header.Set(TenantHeader, cc.runtimeConfig.Tenant)

var response GQLResponse[graphql.RuntimeExt]
err := cc.gqlClient.Do(ctx, req, &response)
if err != nil {
return graphql.RuntimeExt{}, err
}
if response.Result == nil {
return graphql.RuntimeExt{}, errors.New("getRuntime query returned nil response")
}

return *response.Result, nil
}

func (cc *directorClient) updateRuntime(ctx context.Context, id string, directorInput *graphql.RuntimeInput) error {

if directorInput == nil {
return errors.New("Cannot update runtime in Director: missing Runtime config")
}

runtimeInput, err := cc.graphqlizer.RuntimeUpdateInputToGQL(*directorInput)
if err != nil {
return err
}
runtimeQuery := cc.queryProvider.updateRuntimeMutation(id, runtimeInput)

req := gcli.NewRequest(runtimeQuery)
req.Header.Set(TenantHeader, cc.runtimeConfig.Tenant)

var response GQLResponse[graphql.Runtime]
err = cc.gqlClient.Do(ctx, req, &response)
if err != nil {
return err
}
if response.Result == nil {
return errors.New("getRuntime query returned nil response")
}

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,19 @@ const (
value
}
}`

expectedGetRuntimeQuery = `query {
result: runtime(id: "runtimeId") {
id name description labels
}}`
expectedUpdateRuntimeMutation = `mutation {
result: updateRuntime(id: "runtimeId" in: {
name: "Runtime Test name",
labels: {label1:"something",label2:"something2",},
statusCondition: CONNECTED,
}) {
id
}}`
)

var runtimeConfig = config.RuntimeConfig{
Expand Down Expand Up @@ -461,3 +474,139 @@ func TestConfigClient_SetURLsLabels(t *testing.T) {
assert.Nil(t, updatedLabels)
})
}

func TestConfigClient_SetRuntimeStatusCondition(t *testing.T) {
getRuntimeReq := gcli.NewRequest(expectedGetRuntimeQuery)
getRuntimeReq.Header.Set(TenantHeader, "tenant")

updateRuntimeReq := gcli.NewRequest(expectedUpdateRuntimeMutation)
updateRuntimeReq.Header.Set(TenantHeader, "tenant")

expectedSuccessfulGetRuntimeResponse := &graphql.RuntimeExt{
Runtime: graphql.Runtime{
ID: "runtimeId",
Name: "Runtime Test name",
},
Labels: graphql.Labels{
"label1": "something", "label2": "something2",
},
}

expectedUpdateRuntimeResponse := &graphql.Runtime{
ID: "runtimeId",
Name: "Runtime Test name",
}

t.Run("should set runtime status", func(t *testing.T) {
client := &mocks.Client{}

client.
On("Do", context.Background(), getRuntimeReq, &GQLResponse[graphql.RuntimeExt]{}).
Return(nil).
Run(setExpectedResponse[graphql.RuntimeExt](t, expectedSuccessfulGetRuntimeResponse)).
Once()

client.
On("Do", context.Background(), updateRuntimeReq, &GQLResponse[graphql.Runtime]{}).
Return(nil).
Run(setExpectedResponse[graphql.Runtime](t, expectedUpdateRuntimeResponse)).
Once()

configClient := NewConfigurationClient(client, runtimeConfig)

// when
err := configClient.SetRuntimeStatusCondition(context.Background(), graphql.RuntimeStatusConditionConnected)

// then
assert.NoError(t, err)
})

t.Run("should fail when GraphQL query was not executed correctly", func(t *testing.T) {
type testCase struct {
description string
getClientMock func() *mocks.Client
}

for _, tc := range []testCase{
{
description: "should fail when failed to get runtime",
getClientMock: func() *mocks.Client {
client := &mocks.Client{}
client.
On("Do", context.Background(), mock.Anything, mock.Anything).
Return(errors.New("error")).
Once()

return client
},
},
{
description: "should fail when result returned from getRuntime query is nil",
getClientMock: func() *mocks.Client {
client := &mocks.Client{}
client.
On("Do", context.Background(), mock.Anything, mock.Anything).
Return(nil).
Run(setExpectedResponse[graphql.RuntimeExt](t, nil)).
Once()

return client
},
},
{
description: "should fail when failed to update runtime",
getClientMock: func() *mocks.Client {
client := &mocks.Client{}
client.
On("Do", context.Background(), getRuntimeReq, &GQLResponse[graphql.RuntimeExt]{}).
Return(nil).
Run(setExpectedResponse[graphql.RuntimeExt](t, expectedSuccessfulGetRuntimeResponse)).
Once()

client.
On("Do", context.Background(), updateRuntimeReq, &GQLResponse[graphql.Runtime]{}).
Return(errors.New("error")).
Once()
return client
},
},
{
description: "should fail when failed to update runtime",
getClientMock: func() *mocks.Client {
client := &mocks.Client{}
client.
On("Do", context.Background(), getRuntimeReq, &GQLResponse[graphql.RuntimeExt]{}).
Return(nil).
Run(setExpectedResponse[graphql.RuntimeExt](t, expectedSuccessfulGetRuntimeResponse)).
Once()

client.
On("Do", context.Background(), updateRuntimeReq, &GQLResponse[graphql.Runtime]{}).
Return(nil).
Run(setExpectedResponse[graphql.Runtime](t, nil)).
Once()

return client
},
},
} {
configClient := NewConfigurationClient(tc.getClientMock(), runtimeConfig)

// when
err := configClient.SetRuntimeStatusCondition(context.Background(), graphql.RuntimeStatusConditionConnected)

// then
assert.Error(t, err)
}

})
}

func setExpectedResponse[T graphql.RuntimeExt | graphql.Runtime](t *testing.T, expectedResult *T) func(args mock.Arguments) {
return func(args mock.Arguments) {
response, ok := args[2].(*GQLResponse[T])
require.True(t, ok)
assert.Empty(t, response.Result)
response.Result = expectedResult
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package director

import (
"bytes"
"encoding/json"
sprig "github.com/go-task/slim-sprig"
"github.com/kyma-incubator/compass/components/director/pkg/graphql"
"github.com/pkg/errors"
"reflect"
"text/template"
)

type Graphqlizer struct{}

func (g *Graphqlizer) RuntimeUpdateInputToGQL(in graphql.RuntimeInput) (string, error) {
return g.genericToGQL(in, `{
name: "{{.Name}}",
{{- if .Description }}
description: "{{.Description}}",
{{- end }}
{{- if .Labels }}
labels: {{ LabelsToGQL .Labels}},
{{- end }}
{{- if .StatusCondition }}
statusCondition: {{ .StatusCondition }},
{{- end }}
}`)
}

// LabelsToGQL missing godoc
func (g *Graphqlizer) LabelsToGQL(in graphql.Labels) (string, error) {
return g.marshal(in), nil
}

func (g *Graphqlizer) genericToGQL(obj interface{}, tmpl string) (string, error) {
fm := sprig.TxtFuncMap()
fm["marshal"] = g.marshal
fm["LabelsToGQL"] = g.LabelsToGQL

t, err := template.New("tmpl").Funcs(fm).Parse(tmpl)
if err != nil {
return "", errors.Wrapf(err, "while parsing template")
}

var b bytes.Buffer

if err := t.Execute(&b, obj); err != nil {
return "", errors.Wrap(err, "while executing template")
}
return b.String(), nil
}
func (g *Graphqlizer) marshal(obj interface{}) string {
var out string

val := reflect.ValueOf(obj)

switch val.Kind() {
case reflect.Map:
s, err := g.genericToGQL(obj, `{ {{- range $k, $v := . }}{{ $k }}:{{ marshal $v }},{{ end -}} }`)
if err != nil {
return ""
}
out = s
case reflect.Slice, reflect.Array:
s, err := g.genericToGQL(obj, `[{{ range $i, $e := . }}{{ if $i }},{{ end }}{{ marshal $e }}{{ end }}]`)
if err != nil {
return ""
}
out = s
default:
marshalled, err := json.Marshal(obj)
if err != nil {
return ""
}
out = string(marshalled)
}

return out
}
Loading
Loading