From 4061fd8d86d2895f446494f0ecd296c4a68ccb25 Mon Sep 17 00:00:00 2001 From: Amit Lichtenberg Date: Thu, 24 Apr 2025 00:38:46 +0300 Subject: [PATCH 1/7] WIP solve slowness in namespaces query causing otterize clientintents export delays --- src/cmd/accessgraph/get/get-accessgraph.go | 49 +- .../export/export-clientintents.go | 51 +- src/pkg/cloudclient/graphql/client.go | 145 ++- .../graphql/{ => cloudapi}/generate.go | 2 +- .../cloudclient/graphql/cloudapi/generated.go | 1150 +++++++++++++++++ .../graphql/cloudapi/genqlient.graphql | 87 ++ .../graphql/{ => cloudapi}/genqlient.yaml | 0 .../graphql/cloudapi/graphql.config.yml | 2 + .../graphql/{ => cloudapi}/schema.graphql | 19 +- src/pkg/cloudclient/graphql/generated.go | 261 ---- src/pkg/cloudclient/graphql/genqlient.graphql | 23 - .../resources/clusters.go | 38 +- .../resources/environments.go | 30 +- .../graphql/resources/namespaces.go | 103 ++ .../cloudclient/graphql/resources/resolver.go | 71 + .../cloudclient/graphql/resources/services.go | 138 ++ src/pkg/cloudclient/restapi/client.go | 8 +- .../cloudclient/restapi/cloudapi/api.gen.go | 1 - .../cloudclient/restapi/cloudapi/openapi.json | 14 +- .../restapi/resources/namespaces.go | 99 -- .../cloudclient/restapi/resources/resolver.go | 144 --- .../cloudclient/restapi/resources/services.go | 121 -- src/pkg/output/formatters.go | 6 +- src/pkg/telemetry/telemetrysender/sender.go | 2 +- 24 files changed, 1822 insertions(+), 742 deletions(-) rename src/pkg/cloudclient/graphql/{ => cloudapi}/generate.go (93%) create mode 100644 src/pkg/cloudclient/graphql/cloudapi/generated.go create mode 100644 src/pkg/cloudclient/graphql/cloudapi/genqlient.graphql rename src/pkg/cloudclient/graphql/{ => cloudapi}/genqlient.yaml (100%) create mode 100644 src/pkg/cloudclient/graphql/cloudapi/graphql.config.yml rename src/pkg/cloudclient/graphql/{ => cloudapi}/schema.graphql (98%) delete mode 100644 src/pkg/cloudclient/graphql/generated.go delete mode 100644 src/pkg/cloudclient/graphql/genqlient.graphql rename src/pkg/cloudclient/{restapi => graphql}/resources/clusters.go (50%) rename src/pkg/cloudclient/{restapi => graphql}/resources/environments.go (50%) create mode 100644 src/pkg/cloudclient/graphql/resources/namespaces.go create mode 100644 src/pkg/cloudclient/graphql/resources/resolver.go create mode 100644 src/pkg/cloudclient/graphql/resources/services.go delete mode 100644 src/pkg/cloudclient/restapi/resources/namespaces.go delete mode 100644 src/pkg/cloudclient/restapi/resources/resolver.go delete mode 100644 src/pkg/cloudclient/restapi/resources/services.go diff --git a/src/cmd/accessgraph/get/get-accessgraph.go b/src/cmd/accessgraph/get/get-accessgraph.go index 31430ccb..64b7b02f 100644 --- a/src/cmd/accessgraph/get/get-accessgraph.go +++ b/src/cmd/accessgraph/get/get-accessgraph.go @@ -4,9 +4,10 @@ import ( "context" "fmt" "github.com/otterize/otterize-cli/src/pkg/cli" + cloudclientgql "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql" + "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/resources" cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi" "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/cloudapi" - "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/resources" "github.com/otterize/otterize-cli/src/pkg/config" "github.com/otterize/otterize-cli/src/pkg/output" "github.com/otterize/otterize-cli/src/pkg/utils/must" @@ -53,7 +54,7 @@ var GetAccessGraph = &cobra.Command{ return err } - filter, err := accessGraphFilterFromFlags(ctxTimeout, c) + filter, err := accessGraphFilterFromFlags(ctxTimeout) if err != nil { return err } @@ -68,38 +69,66 @@ var GetAccessGraph = &cobra.Command{ }, } -func accessGraphFilterFromFlags(ctx context.Context, c *cloudclient.Client) (cloudapi.InputAccessGraphFilter, error) { - resolver := resources.NewResolver(c).WithContext(ctx) +func toIncludeFilterIfNonEmpty(items []string) *map[string]any { + if len(items) == 0 { + return nil + } + return &map[string]any{ + "include": lo.ToPtr(items), + } +} + +func accessGraphFilterFromFlags(ctx context.Context) (cloudapi.InputAccessGraphFilter, error) { + gqlClient, err := cloudclientgql.NewClient(ctx) + if err != nil { + return cloudapi.InputAccessGraphFilter{}, err + } + + resolver := resources.NewResolver(gqlClient) + if err := resolver.LoadOrgResources(ctx); err != nil { + return cloudapi.InputAccessGraphFilter{}, err + } + + filter := cloudapi.InputAccessGraphFilter{} if viper.IsSet(cli.ClustersKey) || viper.IsSet(clustersIdsKey) { clusters := slices.Concat(viper.GetStringSlice(cli.ClustersKey), viper.GetStringSlice(clustersIdsKey)) - if err := resolver.LoadClusters(clusters); err != nil { + clusterIds, err := resolver.ResolveClusters(clusters) + if err != nil { return cloudapi.InputAccessGraphFilter{}, err } + filter.ClusterIds = toIncludeFilterIfNonEmpty(clusterIds) } if viper.IsSet(cli.EnvironmentsKey) || viper.IsSet(envIdsKey) { envs := slices.Concat(viper.GetStringSlice(cli.EnvironmentsKey), viper.GetStringSlice(envIdsKey)) - if err := resolver.LoadEnvironments(envs); err != nil { + envIds, err := resolver.ResolveEnvironments(envs) + if err != nil { return cloudapi.InputAccessGraphFilter{}, err } + filter.EnvironmentIds = toIncludeFilterIfNonEmpty(envIds) } if viper.IsSet(cli.NamespacesKey) || viper.IsSet(namespacesIdsKey) { namespaces := slices.Concat(viper.GetStringSlice(cli.NamespacesKey), viper.GetStringSlice(namespacesIdsKey)) - if err := resolver.LoadNamespaces(namespaces); err != nil { + namespaceIds, err := resolver.ResolveNamespaces(namespaces) + if err != nil { return cloudapi.InputAccessGraphFilter{}, err } + filter.NamespaceIds = toIncludeFilterIfNonEmpty(namespaceIds) } if viper.IsSet(cli.ServicesKey) || viper.IsSet(servicesIdsKey) { + if err := resolver.LoadServices(ctx); err != nil { + return cloudapi.InputAccessGraphFilter{}, err + } services := slices.Concat(viper.GetStringSlice(cli.ServicesKey), viper.GetStringSlice(servicesIdsKey)) - if err := resolver.LoadServices(services); err != nil { + serviceIds, err := resolver.ResolveServices(services) + if err != nil { return cloudapi.InputAccessGraphFilter{}, err } + filter.ServiceIds = toIncludeFilterIfNonEmpty(serviceIds) } - filter := resolver.BuildAccessGraphFilter() - lastSeenFilter, err := getInputTimeFilterValueFromViper(lastSeenAfterKey) if err != nil { return cloudapi.InputAccessGraphFilter{}, err diff --git a/src/cmd/clientintents/export/export-clientintents.go b/src/cmd/clientintents/export/export-clientintents.go index 108c381d..690278ed 100644 --- a/src/cmd/clientintents/export/export-clientintents.go +++ b/src/cmd/clientintents/export/export-clientintents.go @@ -4,9 +4,10 @@ import ( "context" "fmt" "github.com/otterize/otterize-cli/src/pkg/cli" + cloudclientgql "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql" + "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/resources" cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi" "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/cloudapi" - "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/resources" "github.com/otterize/otterize-cli/src/pkg/config" "github.com/otterize/otterize-cli/src/pkg/errors" "github.com/samber/lo" @@ -43,7 +44,7 @@ var ExportClientIntentsCmd = &cobra.Command{ return errors.Wrap(err) } - filter, err := servicesFilterFromFlags(ctxTimeout, c) + filter, err := servicesFilterFromFlags(ctxTimeout) if err != nil { return errors.Wrap(err) } @@ -85,29 +86,61 @@ var ExportClientIntentsCmd = &cobra.Command{ }, } -func servicesFilterFromFlags(ctx context.Context, c *cloudclient.Client) (cloudapi.InputServiceFilter, error) { - resolver := resources.NewResolver(c).WithContext(ctx) +func toPtrIfNonEmpty(items []string) *[]string { + if len(items) == 0 { + return nil + } + return lo.ToPtr(items) +} + +func servicesFilterFromFlags(ctx context.Context) (cloudapi.InputServiceFilter, error) { + gqlClient, err := cloudclientgql.NewClient(ctx) + if err != nil { + return cloudapi.InputServiceFilter{}, err + } + + resolver := resources.NewResolver(gqlClient) + if err := resolver.LoadOrgResources(ctx); err != nil { + return cloudapi.InputServiceFilter{}, err + } + + filter := cloudapi.InputServiceFilter{} if viper.IsSet(cli.ClustersKey) { - if err := resolver.LoadClusters(viper.GetStringSlice(cli.ClustersKey)); err != nil { + clusterIds, err := resolver.ResolveClusters(viper.GetStringSlice(cli.ClustersKey)) + if err != nil { return cloudapi.InputServiceFilter{}, err } + filter.ClusterIds = toPtrIfNonEmpty(clusterIds) } + if viper.IsSet(cli.EnvironmentsKey) { - if err := resolver.LoadEnvironments(viper.GetStringSlice(cli.EnvironmentsKey)); err != nil { + environmentIds, err := resolver.ResolveEnvironments(viper.GetStringSlice(cli.EnvironmentsKey)) + if err != nil { return cloudapi.InputServiceFilter{}, err } + filter.EnvironmentIds = toPtrIfNonEmpty(environmentIds) } + if viper.IsSet(cli.NamespacesKey) { - if err := resolver.LoadNamespaces(viper.GetStringSlice(cli.NamespacesKey)); err != nil { + namespaceIds, err := resolver.ResolveNamespaces(viper.GetStringSlice(cli.NamespacesKey)) + if err != nil { return cloudapi.InputServiceFilter{}, err } + filter.NamespaceIds = toPtrIfNonEmpty(namespaceIds) } + if viper.IsSet(cli.ServicesKey) { - if err := resolver.LoadServices(viper.GetStringSlice(cli.ServicesKey)); err != nil { + if err := resolver.LoadServices(ctx); err != nil { + return cloudapi.InputServiceFilter{}, err + } + + serviceIds, err := resolver.ResolveServices(viper.GetStringSlice(cli.ServicesKey)) + if err != nil { return cloudapi.InputServiceFilter{}, err } + filter.ServiceIds = toPtrIfNonEmpty(serviceIds) } - return resolver.BuildServicesFilter(), nil + return filter, nil } func init() { diff --git a/src/pkg/cloudclient/graphql/client.go b/src/pkg/cloudclient/graphql/client.go index d25c668a..1f943ece 100644 --- a/src/pkg/cloudclient/graphql/client.go +++ b/src/pkg/cloudclient/graphql/client.go @@ -1,9 +1,21 @@ package graphql import ( + "bytes" "context" genqlientgraphql "github.com/Khan/genqlient/graphql" + "github.com/google/uuid" + "github.com/otterize/otterize-cli/src/pkg/cloudclient/auth" + "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/cloudapi" + "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi" + "github.com/otterize/otterize-cli/src/pkg/config" + "github.com/samber/lo" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" "golang.org/x/oauth2" + "io" + "net/http" + "time" ) type Client struct { @@ -11,12 +23,64 @@ type Client struct { Client genqlientgraphql.Client } +func NewClient(ctx context.Context) (*Client, error) { + orgID, found := restapi.ResolveOrgID() // TODO: move to shared location + if !found { // Shouldn't happen after login + return nil, restapi.ErrNoOrganization + } + + token := auth.GetAPIToken(ctx) + tokenSrc := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) + + return NewClientFromTokenSourceAndOrgID(viper.GetString(config.OtterizeAPIAddressKey), tokenSrc, orgID), nil +} + func NewClientFromToken(address string, token string) *Client { oauth2Token := &oauth2.Token{AccessToken: token} - return NewClient(address, oauth2.StaticTokenSource(oauth2Token)) + return NewClientFromTokenSource(address, oauth2.StaticTokenSource(oauth2Token)) +} + +type SetOrgHeaderDoer struct { + orgID string + client genqlientgraphql.Doer } -func NewClient(address string, tokenSrc oauth2.TokenSource) *Client { +func (d *SetOrgHeaderDoer) Do(req *http.Request) (*http.Response, error) { + id := uuid.New().String() + before := time.Now() + body, err := io.ReadAll(req.Body) + if err != nil { + return nil, err + } + logrus.WithField("method", req.Method).WithField("url", req.URL). + WithField("id", id).WithField("req", string(body)). + Debug("GraphQL request") + + req.Body = io.NopCloser(bytes.NewBuffer(body)) + req.Header.Set("X-Otterize-Organization", d.orgID) + res, err := d.client.Do(req) + + after := time.Now() + duration := after.Sub(before) + logrus.WithField("method", req.Method).WithField("url", req.URL). + WithField("id", id).WithField("duration", duration). + Debug("GraphQL request done") + + return res, err +} + +func NewClientFromTokenSourceAndOrgID(address string, tokenSrc oauth2.TokenSource, orgID string) *Client { + address = address + "/graphql/v1beta" + oauth2client := oauth2.NewClient(context.Background(), tokenSrc) + setHeader := SetOrgHeaderDoer{orgID: orgID, client: oauth2client} + + return &Client{ + Address: address, + Client: genqlientgraphql.NewClient(address, &setHeader), + } +} + +func NewClientFromTokenSource(address string, tokenSrc oauth2.TokenSource) *Client { address = address + "/graphql/v1beta" return &Client{ Address: address, @@ -24,11 +88,82 @@ func NewClient(address string, tokenSrc oauth2.TokenSource) *Client { } } -func (c *Client) RegisterAuth0User(ctx context.Context) (MeFields, error) { - createUserResponse, err := CreateUserFromAuth0User(ctx, c.Client) +func (c *Client) RegisterAuth0User(ctx context.Context) (cloudapi.MeFields, error) { + createUserResponse, err := cloudapi.CreateUserFromAuth0User(ctx, c.Client) if err != nil { - return MeFields{}, err + return cloudapi.MeFields{}, err } return createUserResponse.Me.RegisterUser.MeFields, nil } + +func (c *Client) ListClusters(ctx context.Context) ([]cloudapi.MinimalClusterFields, error) { + response, err := cloudapi.ListCluster(ctx, c.Client) + if err != nil { + return nil, err + } + + return lo.Map(response.Clusters, func(c cloudapi.ListClusterClustersCluster, _ int) cloudapi.MinimalClusterFields { + return c.MinimalClusterFields + }), nil +} + +func (c *Client) ListNamespaces(ctx context.Context) ([]cloudapi.MinimalNamespaceFields, error) { + response, err := cloudapi.ListNamespaces(ctx, c.Client) + if err != nil { + return nil, err + } + + return lo.Map(response.Namespaces, func(ns cloudapi.ListNamespacesNamespacesNamespace, _ int) cloudapi.MinimalNamespaceFields { + return ns.MinimalNamespaceFields + }), nil +} + +func (c *Client) ListServices(ctx context.Context) ([]cloudapi.MinimalServiceFields, error) { + response, err := cloudapi.ListServices(ctx, c.Client) + if err != nil { + return nil, err + } + + return lo.Map(response.Services, func(s cloudapi.ListServicesServicesService, _ int) cloudapi.MinimalServiceFields { + return s.MinimalServiceFields + }), nil +} + +func (c *Client) ListEnvironments(ctx context.Context) ([]cloudapi.MinimalEnvironmentFields, error) { + response, err := cloudapi.ListEnvironments(ctx, c.Client) + if err != nil { + return nil, err + } + + return lo.Map(response.Environments, func(e cloudapi.ListEnvironmentsEnvironmentsEnvironment, _ int) cloudapi.MinimalEnvironmentFields { + return e.MinimalEnvironmentFields + }), nil +} + +type OrgResources struct { + Environments []cloudapi.MinimalEnvironmentFields + Clusters []cloudapi.MinimalClusterFields + Namespaces []cloudapi.MinimalNamespaceFields +} + +func (c *Client) LoadOrgResources(ctx context.Context) (OrgResources, error) { + r := OrgResources{} + + response, err := cloudapi.LoadOrgResources(ctx, c.Client) + if err != nil { + return r, err + } + + r.Environments = lo.Map(response.Environments, func(e cloudapi.LoadOrgResourcesEnvironmentsEnvironment, _ int) cloudapi.MinimalEnvironmentFields { + return e.MinimalEnvironmentFields + }) + r.Clusters = lo.Map(response.Clusters, func(c cloudapi.LoadOrgResourcesClustersCluster, _ int) cloudapi.MinimalClusterFields { + return c.MinimalClusterFields + }) + r.Namespaces = lo.Map(response.Namespaces, func(ns cloudapi.LoadOrgResourcesNamespacesNamespace, _ int) cloudapi.MinimalNamespaceFields { + return ns.MinimalNamespaceFields + }) + + return r, nil +} diff --git a/src/pkg/cloudclient/graphql/generate.go b/src/pkg/cloudclient/graphql/cloudapi/generate.go similarity index 93% rename from src/pkg/cloudclient/graphql/generate.go rename to src/pkg/cloudclient/graphql/cloudapi/generate.go index 4407324c..0d01c8a0 100644 --- a/src/pkg/cloudclient/graphql/generate.go +++ b/src/pkg/cloudclient/graphql/cloudapi/generate.go @@ -1,4 +1,4 @@ -package graphql +package cloudapi import _ "github.com/suessflorian/gqlfetch" diff --git a/src/pkg/cloudclient/graphql/cloudapi/generated.go b/src/pkg/cloudclient/graphql/cloudapi/generated.go new file mode 100644 index 00000000..aec8067e --- /dev/null +++ b/src/pkg/cloudclient/graphql/cloudapi/generated.go @@ -0,0 +1,1150 @@ +// Code generated by github.com/Khan/genqlient, DO NOT EDIT. + +package cloudapi + +import ( + "context" + "encoding/json" + + "github.com/Khan/genqlient/graphql" +) + +type CLICommand struct { + Noun string `json:"noun"` + Verb string `json:"verb"` + Modifiers []string `json:"modifiers"` +} + +// GetNoun returns CLICommand.Noun, and is useful for accessing the field via an interface. +func (v *CLICommand) GetNoun() string { return v.Noun } + +// GetVerb returns CLICommand.Verb, and is useful for accessing the field via an interface. +func (v *CLICommand) GetVerb() string { return v.Verb } + +// GetModifiers returns CLICommand.Modifiers, and is useful for accessing the field via an interface. +func (v *CLICommand) GetModifiers() []string { return v.Modifiers } + +type CLIIdentifier struct { + Version string `json:"version"` + ContextId string `json:"contextId"` + CloudClientId string `json:"cloudClientId"` +} + +// GetVersion returns CLIIdentifier.Version, and is useful for accessing the field via an interface. +func (v *CLIIdentifier) GetVersion() string { return v.Version } + +// GetContextId returns CLIIdentifier.ContextId, and is useful for accessing the field via an interface. +func (v *CLIIdentifier) GetContextId() string { return v.ContextId } + +// GetCloudClientId returns CLIIdentifier.CloudClientId, and is useful for accessing the field via an interface. +func (v *CLIIdentifier) GetCloudClientId() string { return v.CloudClientId } + +type CLITelemetry struct { + Identifier CLIIdentifier `json:"identifier"` + Command CLICommand `json:"command"` +} + +// GetIdentifier returns CLITelemetry.Identifier, and is useful for accessing the field via an interface. +func (v *CLITelemetry) GetIdentifier() CLIIdentifier { return v.Identifier } + +// GetCommand returns CLITelemetry.Command, and is useful for accessing the field via an interface. +func (v *CLITelemetry) GetCommand() CLICommand { return v.Command } + +// CreateUserFromAuth0UserMeMeMutation includes the requested fields of the GraphQL type MeMutation. +type CreateUserFromAuth0UserMeMeMutation struct { + // Register the user defined by the active session token into the otterize users store. + RegisterUser CreateUserFromAuth0UserMeMeMutationRegisterUserMe `json:"registerUser"` +} + +// GetRegisterUser returns CreateUserFromAuth0UserMeMeMutation.RegisterUser, and is useful for accessing the field via an interface. +func (v *CreateUserFromAuth0UserMeMeMutation) GetRegisterUser() CreateUserFromAuth0UserMeMeMutationRegisterUserMe { + return v.RegisterUser +} + +// CreateUserFromAuth0UserMeMeMutationRegisterUserMe includes the requested fields of the GraphQL type Me. +type CreateUserFromAuth0UserMeMeMutationRegisterUserMe struct { + MeFields `json:"-"` +} + +// GetUser returns CreateUserFromAuth0UserMeMeMutationRegisterUserMe.User, and is useful for accessing the field via an interface. +func (v *CreateUserFromAuth0UserMeMeMutationRegisterUserMe) GetUser() MeFieldsUser { + return v.MeFields.User +} + +// GetOrganizations returns CreateUserFromAuth0UserMeMeMutationRegisterUserMe.Organizations, and is useful for accessing the field via an interface. +func (v *CreateUserFromAuth0UserMeMeMutationRegisterUserMe) GetOrganizations() []MeFieldsOrganizationsOrganization { + return v.MeFields.Organizations +} + +func (v *CreateUserFromAuth0UserMeMeMutationRegisterUserMe) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *CreateUserFromAuth0UserMeMeMutationRegisterUserMe + graphql.NoUnmarshalJSON + } + firstPass.CreateUserFromAuth0UserMeMeMutationRegisterUserMe = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MeFields) + if err != nil { + return err + } + return nil +} + +type __premarshalCreateUserFromAuth0UserMeMeMutationRegisterUserMe struct { + User MeFieldsUser `json:"user"` + + Organizations []MeFieldsOrganizationsOrganization `json:"organizations"` +} + +func (v *CreateUserFromAuth0UserMeMeMutationRegisterUserMe) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *CreateUserFromAuth0UserMeMeMutationRegisterUserMe) __premarshalJSON() (*__premarshalCreateUserFromAuth0UserMeMeMutationRegisterUserMe, error) { + var retval __premarshalCreateUserFromAuth0UserMeMeMutationRegisterUserMe + + retval.User = v.MeFields.User + retval.Organizations = v.MeFields.Organizations + return &retval, nil +} + +// CreateUserFromAuth0UserResponse is returned by CreateUserFromAuth0User on success. +type CreateUserFromAuth0UserResponse struct { + // Operate on the current logged-in user + Me CreateUserFromAuth0UserMeMeMutation `json:"me"` +} + +// GetMe returns CreateUserFromAuth0UserResponse.Me, and is useful for accessing the field via an interface. +func (v *CreateUserFromAuth0UserResponse) GetMe() CreateUserFromAuth0UserMeMeMutation { return v.Me } + +// ListClusterClustersCluster includes the requested fields of the GraphQL type Cluster. +type ListClusterClustersCluster struct { + MinimalClusterFields `json:"-"` +} + +// GetId returns ListClusterClustersCluster.Id, and is useful for accessing the field via an interface. +func (v *ListClusterClustersCluster) GetId() string { return v.MinimalClusterFields.Id } + +// GetName returns ListClusterClustersCluster.Name, and is useful for accessing the field via an interface. +func (v *ListClusterClustersCluster) GetName() string { return v.MinimalClusterFields.Name } + +func (v *ListClusterClustersCluster) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *ListClusterClustersCluster + graphql.NoUnmarshalJSON + } + firstPass.ListClusterClustersCluster = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MinimalClusterFields) + if err != nil { + return err + } + return nil +} + +type __premarshalListClusterClustersCluster struct { + Id string `json:"id"` + + Name string `json:"name"` +} + +func (v *ListClusterClustersCluster) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *ListClusterClustersCluster) __premarshalJSON() (*__premarshalListClusterClustersCluster, error) { + var retval __premarshalListClusterClustersCluster + + retval.Id = v.MinimalClusterFields.Id + retval.Name = v.MinimalClusterFields.Name + return &retval, nil +} + +// ListClusterResponse is returned by ListCluster on success. +type ListClusterResponse struct { + // List clusters + Clusters []ListClusterClustersCluster `json:"clusters"` +} + +// GetClusters returns ListClusterResponse.Clusters, and is useful for accessing the field via an interface. +func (v *ListClusterResponse) GetClusters() []ListClusterClustersCluster { return v.Clusters } + +// ListEnvironmentsEnvironmentsEnvironment includes the requested fields of the GraphQL type Environment. +type ListEnvironmentsEnvironmentsEnvironment struct { + MinimalEnvironmentFields `json:"-"` +} + +// GetId returns ListEnvironmentsEnvironmentsEnvironment.Id, and is useful for accessing the field via an interface. +func (v *ListEnvironmentsEnvironmentsEnvironment) GetId() string { + return v.MinimalEnvironmentFields.Id +} + +// GetName returns ListEnvironmentsEnvironmentsEnvironment.Name, and is useful for accessing the field via an interface. +func (v *ListEnvironmentsEnvironmentsEnvironment) GetName() string { + return v.MinimalEnvironmentFields.Name +} + +func (v *ListEnvironmentsEnvironmentsEnvironment) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *ListEnvironmentsEnvironmentsEnvironment + graphql.NoUnmarshalJSON + } + firstPass.ListEnvironmentsEnvironmentsEnvironment = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MinimalEnvironmentFields) + if err != nil { + return err + } + return nil +} + +type __premarshalListEnvironmentsEnvironmentsEnvironment struct { + Id string `json:"id"` + + Name string `json:"name"` +} + +func (v *ListEnvironmentsEnvironmentsEnvironment) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *ListEnvironmentsEnvironmentsEnvironment) __premarshalJSON() (*__premarshalListEnvironmentsEnvironmentsEnvironment, error) { + var retval __premarshalListEnvironmentsEnvironmentsEnvironment + + retval.Id = v.MinimalEnvironmentFields.Id + retval.Name = v.MinimalEnvironmentFields.Name + return &retval, nil +} + +// ListEnvironmentsResponse is returned by ListEnvironments on success. +type ListEnvironmentsResponse struct { + // List environments + Environments []ListEnvironmentsEnvironmentsEnvironment `json:"environments"` +} + +// GetEnvironments returns ListEnvironmentsResponse.Environments, and is useful for accessing the field via an interface. +func (v *ListEnvironmentsResponse) GetEnvironments() []ListEnvironmentsEnvironmentsEnvironment { + return v.Environments +} + +// ListNamespacesNamespacesNamespace includes the requested fields of the GraphQL type Namespace. +type ListNamespacesNamespacesNamespace struct { + MinimalNamespaceFields `json:"-"` +} + +// GetId returns ListNamespacesNamespacesNamespace.Id, and is useful for accessing the field via an interface. +func (v *ListNamespacesNamespacesNamespace) GetId() string { return v.MinimalNamespaceFields.Id } + +// GetName returns ListNamespacesNamespacesNamespace.Name, and is useful for accessing the field via an interface. +func (v *ListNamespacesNamespacesNamespace) GetName() string { return v.MinimalNamespaceFields.Name } + +// GetCluster returns ListNamespacesNamespacesNamespace.Cluster, and is useful for accessing the field via an interface. +func (v *ListNamespacesNamespacesNamespace) GetCluster() MinimalNamespaceFieldsCluster { + return v.MinimalNamespaceFields.Cluster +} + +func (v *ListNamespacesNamespacesNamespace) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *ListNamespacesNamespacesNamespace + graphql.NoUnmarshalJSON + } + firstPass.ListNamespacesNamespacesNamespace = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MinimalNamespaceFields) + if err != nil { + return err + } + return nil +} + +type __premarshalListNamespacesNamespacesNamespace struct { + Id string `json:"id"` + + Name string `json:"name"` + + Cluster MinimalNamespaceFieldsCluster `json:"cluster"` +} + +func (v *ListNamespacesNamespacesNamespace) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *ListNamespacesNamespacesNamespace) __premarshalJSON() (*__premarshalListNamespacesNamespacesNamespace, error) { + var retval __premarshalListNamespacesNamespacesNamespace + + retval.Id = v.MinimalNamespaceFields.Id + retval.Name = v.MinimalNamespaceFields.Name + retval.Cluster = v.MinimalNamespaceFields.Cluster + return &retval, nil +} + +// ListNamespacesResponse is returned by ListNamespaces on success. +type ListNamespacesResponse struct { + // List namespaces + Namespaces []ListNamespacesNamespacesNamespace `json:"namespaces"` +} + +// GetNamespaces returns ListNamespacesResponse.Namespaces, and is useful for accessing the field via an interface. +func (v *ListNamespacesResponse) GetNamespaces() []ListNamespacesNamespacesNamespace { + return v.Namespaces +} + +// ListServicesResponse is returned by ListServices on success. +type ListServicesResponse struct { + // List services + Services []ListServicesServicesService `json:"services"` +} + +// GetServices returns ListServicesResponse.Services, and is useful for accessing the field via an interface. +func (v *ListServicesResponse) GetServices() []ListServicesServicesService { return v.Services } + +// ListServicesServicesService includes the requested fields of the GraphQL type Service. +type ListServicesServicesService struct { + MinimalServiceFields `json:"-"` +} + +// GetId returns ListServicesServicesService.Id, and is useful for accessing the field via an interface. +func (v *ListServicesServicesService) GetId() string { return v.MinimalServiceFields.Id } + +// GetName returns ListServicesServicesService.Name, and is useful for accessing the field via an interface. +func (v *ListServicesServicesService) GetName() string { return v.MinimalServiceFields.Name } + +// GetNamespace returns ListServicesServicesService.Namespace, and is useful for accessing the field via an interface. +func (v *ListServicesServicesService) GetNamespace() *MinimalServiceFieldsNamespace { + return v.MinimalServiceFields.Namespace +} + +func (v *ListServicesServicesService) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *ListServicesServicesService + graphql.NoUnmarshalJSON + } + firstPass.ListServicesServicesService = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MinimalServiceFields) + if err != nil { + return err + } + return nil +} + +type __premarshalListServicesServicesService struct { + Id string `json:"id"` + + Name string `json:"name"` + + Namespace *MinimalServiceFieldsNamespace `json:"namespace"` +} + +func (v *ListServicesServicesService) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *ListServicesServicesService) __premarshalJSON() (*__premarshalListServicesServicesService, error) { + var retval __premarshalListServicesServicesService + + retval.Id = v.MinimalServiceFields.Id + retval.Name = v.MinimalServiceFields.Name + retval.Namespace = v.MinimalServiceFields.Namespace + return &retval, nil +} + +// LoadOrgResourcesClustersCluster includes the requested fields of the GraphQL type Cluster. +type LoadOrgResourcesClustersCluster struct { + MinimalClusterFields `json:"-"` +} + +// GetId returns LoadOrgResourcesClustersCluster.Id, and is useful for accessing the field via an interface. +func (v *LoadOrgResourcesClustersCluster) GetId() string { return v.MinimalClusterFields.Id } + +// GetName returns LoadOrgResourcesClustersCluster.Name, and is useful for accessing the field via an interface. +func (v *LoadOrgResourcesClustersCluster) GetName() string { return v.MinimalClusterFields.Name } + +func (v *LoadOrgResourcesClustersCluster) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *LoadOrgResourcesClustersCluster + graphql.NoUnmarshalJSON + } + firstPass.LoadOrgResourcesClustersCluster = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MinimalClusterFields) + if err != nil { + return err + } + return nil +} + +type __premarshalLoadOrgResourcesClustersCluster struct { + Id string `json:"id"` + + Name string `json:"name"` +} + +func (v *LoadOrgResourcesClustersCluster) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *LoadOrgResourcesClustersCluster) __premarshalJSON() (*__premarshalLoadOrgResourcesClustersCluster, error) { + var retval __premarshalLoadOrgResourcesClustersCluster + + retval.Id = v.MinimalClusterFields.Id + retval.Name = v.MinimalClusterFields.Name + return &retval, nil +} + +// LoadOrgResourcesEnvironmentsEnvironment includes the requested fields of the GraphQL type Environment. +type LoadOrgResourcesEnvironmentsEnvironment struct { + MinimalEnvironmentFields `json:"-"` +} + +// GetId returns LoadOrgResourcesEnvironmentsEnvironment.Id, and is useful for accessing the field via an interface. +func (v *LoadOrgResourcesEnvironmentsEnvironment) GetId() string { + return v.MinimalEnvironmentFields.Id +} + +// GetName returns LoadOrgResourcesEnvironmentsEnvironment.Name, and is useful for accessing the field via an interface. +func (v *LoadOrgResourcesEnvironmentsEnvironment) GetName() string { + return v.MinimalEnvironmentFields.Name +} + +func (v *LoadOrgResourcesEnvironmentsEnvironment) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *LoadOrgResourcesEnvironmentsEnvironment + graphql.NoUnmarshalJSON + } + firstPass.LoadOrgResourcesEnvironmentsEnvironment = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MinimalEnvironmentFields) + if err != nil { + return err + } + return nil +} + +type __premarshalLoadOrgResourcesEnvironmentsEnvironment struct { + Id string `json:"id"` + + Name string `json:"name"` +} + +func (v *LoadOrgResourcesEnvironmentsEnvironment) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *LoadOrgResourcesEnvironmentsEnvironment) __premarshalJSON() (*__premarshalLoadOrgResourcesEnvironmentsEnvironment, error) { + var retval __premarshalLoadOrgResourcesEnvironmentsEnvironment + + retval.Id = v.MinimalEnvironmentFields.Id + retval.Name = v.MinimalEnvironmentFields.Name + return &retval, nil +} + +// LoadOrgResourcesNamespacesNamespace includes the requested fields of the GraphQL type Namespace. +type LoadOrgResourcesNamespacesNamespace struct { + MinimalNamespaceFields `json:"-"` +} + +// GetId returns LoadOrgResourcesNamespacesNamespace.Id, and is useful for accessing the field via an interface. +func (v *LoadOrgResourcesNamespacesNamespace) GetId() string { return v.MinimalNamespaceFields.Id } + +// GetName returns LoadOrgResourcesNamespacesNamespace.Name, and is useful for accessing the field via an interface. +func (v *LoadOrgResourcesNamespacesNamespace) GetName() string { return v.MinimalNamespaceFields.Name } + +// GetCluster returns LoadOrgResourcesNamespacesNamespace.Cluster, and is useful for accessing the field via an interface. +func (v *LoadOrgResourcesNamespacesNamespace) GetCluster() MinimalNamespaceFieldsCluster { + return v.MinimalNamespaceFields.Cluster +} + +func (v *LoadOrgResourcesNamespacesNamespace) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *LoadOrgResourcesNamespacesNamespace + graphql.NoUnmarshalJSON + } + firstPass.LoadOrgResourcesNamespacesNamespace = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MinimalNamespaceFields) + if err != nil { + return err + } + return nil +} + +type __premarshalLoadOrgResourcesNamespacesNamespace struct { + Id string `json:"id"` + + Name string `json:"name"` + + Cluster MinimalNamespaceFieldsCluster `json:"cluster"` +} + +func (v *LoadOrgResourcesNamespacesNamespace) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *LoadOrgResourcesNamespacesNamespace) __premarshalJSON() (*__premarshalLoadOrgResourcesNamespacesNamespace, error) { + var retval __premarshalLoadOrgResourcesNamespacesNamespace + + retval.Id = v.MinimalNamespaceFields.Id + retval.Name = v.MinimalNamespaceFields.Name + retval.Cluster = v.MinimalNamespaceFields.Cluster + return &retval, nil +} + +// LoadOrgResourcesResponse is returned by LoadOrgResources on success. +type LoadOrgResourcesResponse struct { + // List clusters + Clusters []LoadOrgResourcesClustersCluster `json:"clusters"` + // List namespaces + Namespaces []LoadOrgResourcesNamespacesNamespace `json:"namespaces"` + // List environments + Environments []LoadOrgResourcesEnvironmentsEnvironment `json:"environments"` +} + +// GetClusters returns LoadOrgResourcesResponse.Clusters, and is useful for accessing the field via an interface. +func (v *LoadOrgResourcesResponse) GetClusters() []LoadOrgResourcesClustersCluster { return v.Clusters } + +// GetNamespaces returns LoadOrgResourcesResponse.Namespaces, and is useful for accessing the field via an interface. +func (v *LoadOrgResourcesResponse) GetNamespaces() []LoadOrgResourcesNamespacesNamespace { + return v.Namespaces +} + +// GetEnvironments returns LoadOrgResourcesResponse.Environments, and is useful for accessing the field via an interface. +func (v *LoadOrgResourcesResponse) GetEnvironments() []LoadOrgResourcesEnvironmentsEnvironment { + return v.Environments +} + +// MeFields includes the GraphQL fields of Me requested by the fragment MeFields. +type MeFields struct { + // The logged-in user details. + User MeFieldsUser `json:"user"` + // The organizations to which the current logged-in user belongs. + Organizations []MeFieldsOrganizationsOrganization `json:"organizations"` +} + +// GetUser returns MeFields.User, and is useful for accessing the field via an interface. +func (v *MeFields) GetUser() MeFieldsUser { return v.User } + +// GetOrganizations returns MeFields.Organizations, and is useful for accessing the field via an interface. +func (v *MeFields) GetOrganizations() []MeFieldsOrganizationsOrganization { return v.Organizations } + +// MeFieldsOrganizationsOrganization includes the requested fields of the GraphQL type Organization. +type MeFieldsOrganizationsOrganization struct { + Id string `json:"id"` +} + +// GetId returns MeFieldsOrganizationsOrganization.Id, and is useful for accessing the field via an interface. +func (v *MeFieldsOrganizationsOrganization) GetId() string { return v.Id } + +// MeFieldsUser includes the requested fields of the GraphQL type User. +type MeFieldsUser struct { + Id string `json:"id"` + Email string `json:"email"` + Name string `json:"name"` +} + +// GetId returns MeFieldsUser.Id, and is useful for accessing the field via an interface. +func (v *MeFieldsUser) GetId() string { return v.Id } + +// GetEmail returns MeFieldsUser.Email, and is useful for accessing the field via an interface. +func (v *MeFieldsUser) GetEmail() string { return v.Email } + +// GetName returns MeFieldsUser.Name, and is useful for accessing the field via an interface. +func (v *MeFieldsUser) GetName() string { return v.Name } + +// MinimalClusterFields includes the GraphQL fields of Cluster requested by the fragment MinimalClusterFields. +type MinimalClusterFields struct { + Id string `json:"id"` + Name string `json:"name"` +} + +// GetId returns MinimalClusterFields.Id, and is useful for accessing the field via an interface. +func (v *MinimalClusterFields) GetId() string { return v.Id } + +// GetName returns MinimalClusterFields.Name, and is useful for accessing the field via an interface. +func (v *MinimalClusterFields) GetName() string { return v.Name } + +// MinimalEnvironmentFields includes the GraphQL fields of Environment requested by the fragment MinimalEnvironmentFields. +type MinimalEnvironmentFields struct { + Id string `json:"id"` + Name string `json:"name"` +} + +// GetId returns MinimalEnvironmentFields.Id, and is useful for accessing the field via an interface. +func (v *MinimalEnvironmentFields) GetId() string { return v.Id } + +// GetName returns MinimalEnvironmentFields.Name, and is useful for accessing the field via an interface. +func (v *MinimalEnvironmentFields) GetName() string { return v.Name } + +// MinimalNamespaceFields includes the GraphQL fields of Namespace requested by the fragment MinimalNamespaceFields. +type MinimalNamespaceFields struct { + Id string `json:"id"` + Name string `json:"name"` + Cluster MinimalNamespaceFieldsCluster `json:"cluster"` +} + +// GetId returns MinimalNamespaceFields.Id, and is useful for accessing the field via an interface. +func (v *MinimalNamespaceFields) GetId() string { return v.Id } + +// GetName returns MinimalNamespaceFields.Name, and is useful for accessing the field via an interface. +func (v *MinimalNamespaceFields) GetName() string { return v.Name } + +// GetCluster returns MinimalNamespaceFields.Cluster, and is useful for accessing the field via an interface. +func (v *MinimalNamespaceFields) GetCluster() MinimalNamespaceFieldsCluster { return v.Cluster } + +// MinimalNamespaceFieldsCluster includes the requested fields of the GraphQL type Cluster. +type MinimalNamespaceFieldsCluster struct { + MinimalClusterFields `json:"-"` +} + +// GetId returns MinimalNamespaceFieldsCluster.Id, and is useful for accessing the field via an interface. +func (v *MinimalNamespaceFieldsCluster) GetId() string { return v.MinimalClusterFields.Id } + +// GetName returns MinimalNamespaceFieldsCluster.Name, and is useful for accessing the field via an interface. +func (v *MinimalNamespaceFieldsCluster) GetName() string { return v.MinimalClusterFields.Name } + +func (v *MinimalNamespaceFieldsCluster) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *MinimalNamespaceFieldsCluster + graphql.NoUnmarshalJSON + } + firstPass.MinimalNamespaceFieldsCluster = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MinimalClusterFields) + if err != nil { + return err + } + return nil +} + +type __premarshalMinimalNamespaceFieldsCluster struct { + Id string `json:"id"` + + Name string `json:"name"` +} + +func (v *MinimalNamespaceFieldsCluster) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *MinimalNamespaceFieldsCluster) __premarshalJSON() (*__premarshalMinimalNamespaceFieldsCluster, error) { + var retval __premarshalMinimalNamespaceFieldsCluster + + retval.Id = v.MinimalClusterFields.Id + retval.Name = v.MinimalClusterFields.Name + return &retval, nil +} + +// MinimalServiceFields includes the GraphQL fields of Service requested by the fragment MinimalServiceFields. +type MinimalServiceFields struct { + Id string `json:"id"` + Name string `json:"name"` + Namespace *MinimalServiceFieldsNamespace `json:"namespace"` +} + +// GetId returns MinimalServiceFields.Id, and is useful for accessing the field via an interface. +func (v *MinimalServiceFields) GetId() string { return v.Id } + +// GetName returns MinimalServiceFields.Name, and is useful for accessing the field via an interface. +func (v *MinimalServiceFields) GetName() string { return v.Name } + +// GetNamespace returns MinimalServiceFields.Namespace, and is useful for accessing the field via an interface. +func (v *MinimalServiceFields) GetNamespace() *MinimalServiceFieldsNamespace { return v.Namespace } + +// MinimalServiceFieldsNamespace includes the requested fields of the GraphQL type Namespace. +type MinimalServiceFieldsNamespace struct { + MinimalNamespaceFields `json:"-"` +} + +// GetId returns MinimalServiceFieldsNamespace.Id, and is useful for accessing the field via an interface. +func (v *MinimalServiceFieldsNamespace) GetId() string { return v.MinimalNamespaceFields.Id } + +// GetName returns MinimalServiceFieldsNamespace.Name, and is useful for accessing the field via an interface. +func (v *MinimalServiceFieldsNamespace) GetName() string { return v.MinimalNamespaceFields.Name } + +// GetCluster returns MinimalServiceFieldsNamespace.Cluster, and is useful for accessing the field via an interface. +func (v *MinimalServiceFieldsNamespace) GetCluster() MinimalNamespaceFieldsCluster { + return v.MinimalNamespaceFields.Cluster +} + +func (v *MinimalServiceFieldsNamespace) UnmarshalJSON(b []byte) error { + + if string(b) == "null" { + return nil + } + + var firstPass struct { + *MinimalServiceFieldsNamespace + graphql.NoUnmarshalJSON + } + firstPass.MinimalServiceFieldsNamespace = v + + err := json.Unmarshal(b, &firstPass) + if err != nil { + return err + } + + err = json.Unmarshal( + b, &v.MinimalNamespaceFields) + if err != nil { + return err + } + return nil +} + +type __premarshalMinimalServiceFieldsNamespace struct { + Id string `json:"id"` + + Name string `json:"name"` + + Cluster MinimalNamespaceFieldsCluster `json:"cluster"` +} + +func (v *MinimalServiceFieldsNamespace) MarshalJSON() ([]byte, error) { + premarshaled, err := v.__premarshalJSON() + if err != nil { + return nil, err + } + return json.Marshal(premarshaled) +} + +func (v *MinimalServiceFieldsNamespace) __premarshalJSON() (*__premarshalMinimalServiceFieldsNamespace, error) { + var retval __premarshalMinimalServiceFieldsNamespace + + retval.Id = v.MinimalNamespaceFields.Id + retval.Name = v.MinimalNamespaceFields.Name + retval.Cluster = v.MinimalNamespaceFields.Cluster + return &retval, nil +} + +// SendCLITelemetryResponse is returned by SendCLITelemetry on success. +type SendCLITelemetryResponse struct { + SendCLITelemetries bool `json:"sendCLITelemetries"` +} + +// GetSendCLITelemetries returns SendCLITelemetryResponse.SendCLITelemetries, and is useful for accessing the field via an interface. +func (v *SendCLITelemetryResponse) GetSendCLITelemetries() bool { return v.SendCLITelemetries } + +// __SendCLITelemetryInput is used internally by genqlient +type __SendCLITelemetryInput struct { + Telemetry CLITelemetry `json:"telemetry"` +} + +// GetTelemetry returns __SendCLITelemetryInput.Telemetry, and is useful for accessing the field via an interface. +func (v *__SendCLITelemetryInput) GetTelemetry() CLITelemetry { return v.Telemetry } + +// The mutation executed by CreateUserFromAuth0User. +const CreateUserFromAuth0User_Operation = ` +mutation CreateUserFromAuth0User { + me { + registerUser { + ... MeFields + } + } +} +fragment MeFields on Me { + user { + id + email + name + } + organizations { + id + } +} +` + +func CreateUserFromAuth0User( + ctx_ context.Context, + client_ graphql.Client, +) (data_ *CreateUserFromAuth0UserResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "CreateUserFromAuth0User", + Query: CreateUserFromAuth0User_Operation, + } + + data_ = &CreateUserFromAuth0UserResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The query executed by ListCluster. +const ListCluster_Operation = ` +query ListCluster { + clusters { + ... MinimalClusterFields + } +} +fragment MinimalClusterFields on Cluster { + id + name +} +` + +func ListCluster( + ctx_ context.Context, + client_ graphql.Client, +) (data_ *ListClusterResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "ListCluster", + Query: ListCluster_Operation, + } + + data_ = &ListClusterResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The query executed by ListEnvironments. +const ListEnvironments_Operation = ` +query ListEnvironments { + environments { + ... MinimalEnvironmentFields + } +} +fragment MinimalEnvironmentFields on Environment { + id + name +} +` + +func ListEnvironments( + ctx_ context.Context, + client_ graphql.Client, +) (data_ *ListEnvironmentsResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "ListEnvironments", + Query: ListEnvironments_Operation, + } + + data_ = &ListEnvironmentsResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The query executed by ListNamespaces. +const ListNamespaces_Operation = ` +query ListNamespaces { + namespaces { + ... MinimalNamespaceFields + } +} +fragment MinimalNamespaceFields on Namespace { + id + name + cluster { + ... MinimalClusterFields + } +} +fragment MinimalClusterFields on Cluster { + id + name +} +` + +func ListNamespaces( + ctx_ context.Context, + client_ graphql.Client, +) (data_ *ListNamespacesResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "ListNamespaces", + Query: ListNamespaces_Operation, + } + + data_ = &ListNamespacesResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The query executed by ListServices. +const ListServices_Operation = ` +query ListServices { + services { + ... MinimalServiceFields + } +} +fragment MinimalServiceFields on Service { + id + name + namespace { + ... MinimalNamespaceFields + } +} +fragment MinimalNamespaceFields on Namespace { + id + name + cluster { + ... MinimalClusterFields + } +} +fragment MinimalClusterFields on Cluster { + id + name +} +` + +func ListServices( + ctx_ context.Context, + client_ graphql.Client, +) (data_ *ListServicesResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "ListServices", + Query: ListServices_Operation, + } + + data_ = &ListServicesResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The query executed by LoadOrgResources. +const LoadOrgResources_Operation = ` +query LoadOrgResources { + clusters { + ... MinimalClusterFields + } + namespaces { + ... MinimalNamespaceFields + } + environments { + ... MinimalEnvironmentFields + } +} +fragment MinimalClusterFields on Cluster { + id + name +} +fragment MinimalNamespaceFields on Namespace { + id + name + cluster { + ... MinimalClusterFields + } +} +fragment MinimalEnvironmentFields on Environment { + id + name +} +` + +func LoadOrgResources( + ctx_ context.Context, + client_ graphql.Client, +) (data_ *LoadOrgResourcesResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "LoadOrgResources", + Query: LoadOrgResources_Operation, + } + + data_ = &LoadOrgResourcesResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} + +// The mutation executed by SendCLITelemetry. +const SendCLITelemetry_Operation = ` +mutation SendCLITelemetry ($telemetry: CLITelemetry!) { + sendCLITelemetries(telemetries: [$telemetry]) +} +` + +func SendCLITelemetry( + ctx_ context.Context, + client_ graphql.Client, + telemetry CLITelemetry, +) (data_ *SendCLITelemetryResponse, err_ error) { + req_ := &graphql.Request{ + OpName: "SendCLITelemetry", + Query: SendCLITelemetry_Operation, + Variables: &__SendCLITelemetryInput{ + Telemetry: telemetry, + }, + } + + data_ = &SendCLITelemetryResponse{} + resp_ := &graphql.Response{Data: data_} + + err_ = client_.MakeRequest( + ctx_, + req_, + resp_, + ) + + return data_, err_ +} diff --git a/src/pkg/cloudclient/graphql/cloudapi/genqlient.graphql b/src/pkg/cloudclient/graphql/cloudapi/genqlient.graphql new file mode 100644 index 00000000..e1deea33 --- /dev/null +++ b/src/pkg/cloudclient/graphql/cloudapi/genqlient.graphql @@ -0,0 +1,87 @@ +fragment MeFields on Me { + user { + id + email + name + } + organizations { + id + } +} + + +mutation CreateUserFromAuth0User { + me { + registerUser { + ...MeFields + } + } +} + +mutation SendCLITelemetry($telemetry: CLITelemetry!) { + sendCLITelemetries(telemetries: [$telemetry]) +} + +fragment MinimalClusterFields on Cluster { + id + name +} + +query ListCluster { + clusters { + ...MinimalClusterFields + } +} + +fragment MinimalNamespaceFields on Namespace { + id + name + cluster { + ...MinimalClusterFields + } +} + +query ListNamespaces { + namespaces { + ...MinimalNamespaceFields + } +} + +fragment MinimalServiceFields on Service { + id + name + # @genqlient(pointer: true) + namespace { + ...MinimalNamespaceFields + } +} + +query ListServices { + services { + ...MinimalServiceFields + } +} + +fragment MinimalEnvironmentFields on Environment { + id + name +} + +query ListEnvironments { + environments { + ...MinimalEnvironmentFields + } +} + +query LoadOrgResources { + clusters { + ...MinimalClusterFields + } + namespaces { + ...MinimalNamespaceFields + } + environments { + ...MinimalEnvironmentFields + } + # services are not pre-loaded as there may be wayy too many +} \ No newline at end of file diff --git a/src/pkg/cloudclient/graphql/genqlient.yaml b/src/pkg/cloudclient/graphql/cloudapi/genqlient.yaml similarity index 100% rename from src/pkg/cloudclient/graphql/genqlient.yaml rename to src/pkg/cloudclient/graphql/cloudapi/genqlient.yaml diff --git a/src/pkg/cloudclient/graphql/cloudapi/graphql.config.yml b/src/pkg/cloudclient/graphql/cloudapi/graphql.config.yml new file mode 100644 index 00000000..8f323a93 --- /dev/null +++ b/src/pkg/cloudclient/graphql/cloudapi/graphql.config.yml @@ -0,0 +1,2 @@ +schema: + - ./schema.graphql \ No newline at end of file diff --git a/src/pkg/cloudclient/graphql/schema.graphql b/src/pkg/cloudclient/graphql/cloudapi/schema.graphql similarity index 98% rename from src/pkg/cloudclient/graphql/schema.graphql rename to src/pkg/cloudclient/graphql/cloudapi/schema.graphql index 6f6ffe52..257b6249 100644 --- a/src/pkg/cloudclient/graphql/schema.graphql +++ b/src/pkg/cloudclient/graphql/cloudapi/schema.graphql @@ -7,14 +7,17 @@ directive @constraint( example: String! ) on ENUM_VALUE -"""The @defer directive may be specified on a fragment spread to imply de-prioritization, that causes the fragment to be omitted in the initial response, and delivered as a subsequent response afterward. A query with @defer directive will cause the request to potentially return multiple responses, where non-deferred data is delivered in the initial response and data deferred delivered in a subsequent response. @include and @skip take precedence over @defer.""" +"""Directs the executor to defer this fragment when the `if` argument is true or undefined.""" directive @defer( +"""Deferred when true or undefined.""" if: Boolean +"""Unique name""" label: String ) on FRAGMENT_SPREAD | INLINE_FRAGMENT -"""The @deprecated built-in directive is used within the type system definition language to indicate deprecated portions of a GraphQL service's schema, such as deprecated fields on a type, arguments on a field, input fields on an input type, or values of an enum type.""" +"""Marks an element of a GraphQL schema as no longer supported.""" directive @deprecated( +"""Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/).""" reason: String ) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE @@ -27,8 +30,9 @@ directive @httpError( statusCode: Int! ) on ENUM_VALUE -"""The @include directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional inclusion during execution as described by the if argument.""" +"""Directs the executor to include this field or fragment only when the `if` argument is true.""" directive @include( +"""Included when true.""" if: Boolean! ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT @@ -41,6 +45,9 @@ user authentication, meaning anyone and everyone can execute it. USE WITH CAUTIO user authentication, meaning anyone and everyone can execute it. USE WITH CAUTION.""" directive @noauth on FIELD_DEFINITION +"""Indicates exactly one field must be supplied and this field must not be `null`.""" +directive @oneOf on INPUT_OBJECT + """@requiresRole indicates that the specified query / mutation / subscription requires any of the provided roles to be executed. Users without any of the specified roles will not be able to execute the query / mutation / subscription.""" directive @requiresRole( @@ -57,13 +64,15 @@ directive @restApiRoute( tags: [String!]! ) on FIELD_DEFINITION -"""The @skip directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional exclusion during execution as described by the if argument.""" +"""Directs the executor to skip this field or fragment when the `if` argument is true.""" directive @skip( +"""Skipped when true.""" if: Boolean! ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT -"""The @specifiedBy built-in directive is used within the type system definition language to provide a scalar specification URL for specifying the behavior of custom scalar types.""" +"""Exposes a URL that specifies the behavior of this scalar.""" directive @specifiedBy( +"""The URL that specifies the behavior of this scalar.""" url: String! ) on SCALAR diff --git a/src/pkg/cloudclient/graphql/generated.go b/src/pkg/cloudclient/graphql/generated.go deleted file mode 100644 index 473f8eaa..00000000 --- a/src/pkg/cloudclient/graphql/generated.go +++ /dev/null @@ -1,261 +0,0 @@ -// Code generated by github.com/Khan/genqlient, DO NOT EDIT. - -package graphql - -import ( - "context" - "encoding/json" - - "github.com/Khan/genqlient/graphql" -) - -type CLICommand struct { - Noun string `json:"noun"` - Verb string `json:"verb"` - Modifiers []string `json:"modifiers"` -} - -// GetNoun returns CLICommand.Noun, and is useful for accessing the field via an interface. -func (v *CLICommand) GetNoun() string { return v.Noun } - -// GetVerb returns CLICommand.Verb, and is useful for accessing the field via an interface. -func (v *CLICommand) GetVerb() string { return v.Verb } - -// GetModifiers returns CLICommand.Modifiers, and is useful for accessing the field via an interface. -func (v *CLICommand) GetModifiers() []string { return v.Modifiers } - -type CLIIdentifier struct { - Version string `json:"version"` - ContextId string `json:"contextId"` - CloudClientId string `json:"cloudClientId"` -} - -// GetVersion returns CLIIdentifier.Version, and is useful for accessing the field via an interface. -func (v *CLIIdentifier) GetVersion() string { return v.Version } - -// GetContextId returns CLIIdentifier.ContextId, and is useful for accessing the field via an interface. -func (v *CLIIdentifier) GetContextId() string { return v.ContextId } - -// GetCloudClientId returns CLIIdentifier.CloudClientId, and is useful for accessing the field via an interface. -func (v *CLIIdentifier) GetCloudClientId() string { return v.CloudClientId } - -type CLITelemetry struct { - Identifier CLIIdentifier `json:"identifier"` - Command CLICommand `json:"command"` -} - -// GetIdentifier returns CLITelemetry.Identifier, and is useful for accessing the field via an interface. -func (v *CLITelemetry) GetIdentifier() CLIIdentifier { return v.Identifier } - -// GetCommand returns CLITelemetry.Command, and is useful for accessing the field via an interface. -func (v *CLITelemetry) GetCommand() CLICommand { return v.Command } - -// CreateUserFromAuth0UserMeMeMutation includes the requested fields of the GraphQL type MeMutation. -type CreateUserFromAuth0UserMeMeMutation struct { - // Register the user defined by the active session token into the otterize users store. - RegisterUser CreateUserFromAuth0UserMeMeMutationRegisterUserMe `json:"registerUser"` -} - -// GetRegisterUser returns CreateUserFromAuth0UserMeMeMutation.RegisterUser, and is useful for accessing the field via an interface. -func (v *CreateUserFromAuth0UserMeMeMutation) GetRegisterUser() CreateUserFromAuth0UserMeMeMutationRegisterUserMe { - return v.RegisterUser -} - -// CreateUserFromAuth0UserMeMeMutationRegisterUserMe includes the requested fields of the GraphQL type Me. -type CreateUserFromAuth0UserMeMeMutationRegisterUserMe struct { - MeFields `json:"-"` -} - -// GetUser returns CreateUserFromAuth0UserMeMeMutationRegisterUserMe.User, and is useful for accessing the field via an interface. -func (v *CreateUserFromAuth0UserMeMeMutationRegisterUserMe) GetUser() MeFieldsUser { - return v.MeFields.User -} - -// GetOrganizations returns CreateUserFromAuth0UserMeMeMutationRegisterUserMe.Organizations, and is useful for accessing the field via an interface. -func (v *CreateUserFromAuth0UserMeMeMutationRegisterUserMe) GetOrganizations() []MeFieldsOrganizationsOrganization { - return v.MeFields.Organizations -} - -func (v *CreateUserFromAuth0UserMeMeMutationRegisterUserMe) UnmarshalJSON(b []byte) error { - - if string(b) == "null" { - return nil - } - - var firstPass struct { - *CreateUserFromAuth0UserMeMeMutationRegisterUserMe - graphql.NoUnmarshalJSON - } - firstPass.CreateUserFromAuth0UserMeMeMutationRegisterUserMe = v - - err := json.Unmarshal(b, &firstPass) - if err != nil { - return err - } - - err = json.Unmarshal( - b, &v.MeFields) - if err != nil { - return err - } - return nil -} - -type __premarshalCreateUserFromAuth0UserMeMeMutationRegisterUserMe struct { - User MeFieldsUser `json:"user"` - - Organizations []MeFieldsOrganizationsOrganization `json:"organizations"` -} - -func (v *CreateUserFromAuth0UserMeMeMutationRegisterUserMe) MarshalJSON() ([]byte, error) { - premarshaled, err := v.__premarshalJSON() - if err != nil { - return nil, err - } - return json.Marshal(premarshaled) -} - -func (v *CreateUserFromAuth0UserMeMeMutationRegisterUserMe) __premarshalJSON() (*__premarshalCreateUserFromAuth0UserMeMeMutationRegisterUserMe, error) { - var retval __premarshalCreateUserFromAuth0UserMeMeMutationRegisterUserMe - - retval.User = v.MeFields.User - retval.Organizations = v.MeFields.Organizations - return &retval, nil -} - -// CreateUserFromAuth0UserResponse is returned by CreateUserFromAuth0User on success. -type CreateUserFromAuth0UserResponse struct { - // Operate on the current logged-in user - Me CreateUserFromAuth0UserMeMeMutation `json:"me"` -} - -// GetMe returns CreateUserFromAuth0UserResponse.Me, and is useful for accessing the field via an interface. -func (v *CreateUserFromAuth0UserResponse) GetMe() CreateUserFromAuth0UserMeMeMutation { return v.Me } - -// MeFields includes the GraphQL fields of Me requested by the fragment MeFields. -type MeFields struct { - // The logged-in user details. - User MeFieldsUser `json:"user"` - // The organizations to which the current logged-in user belongs. - Organizations []MeFieldsOrganizationsOrganization `json:"organizations"` -} - -// GetUser returns MeFields.User, and is useful for accessing the field via an interface. -func (v *MeFields) GetUser() MeFieldsUser { return v.User } - -// GetOrganizations returns MeFields.Organizations, and is useful for accessing the field via an interface. -func (v *MeFields) GetOrganizations() []MeFieldsOrganizationsOrganization { return v.Organizations } - -// MeFieldsOrganizationsOrganization includes the requested fields of the GraphQL type Organization. -type MeFieldsOrganizationsOrganization struct { - Id string `json:"id"` -} - -// GetId returns MeFieldsOrganizationsOrganization.Id, and is useful for accessing the field via an interface. -func (v *MeFieldsOrganizationsOrganization) GetId() string { return v.Id } - -// MeFieldsUser includes the requested fields of the GraphQL type User. -type MeFieldsUser struct { - Id string `json:"id"` - Email string `json:"email"` - Name string `json:"name"` -} - -// GetId returns MeFieldsUser.Id, and is useful for accessing the field via an interface. -func (v *MeFieldsUser) GetId() string { return v.Id } - -// GetEmail returns MeFieldsUser.Email, and is useful for accessing the field via an interface. -func (v *MeFieldsUser) GetEmail() string { return v.Email } - -// GetName returns MeFieldsUser.Name, and is useful for accessing the field via an interface. -func (v *MeFieldsUser) GetName() string { return v.Name } - -// SendCLITelemetryResponse is returned by SendCLITelemetry on success. -type SendCLITelemetryResponse struct { - SendCLITelemetries bool `json:"sendCLITelemetries"` -} - -// GetSendCLITelemetries returns SendCLITelemetryResponse.SendCLITelemetries, and is useful for accessing the field via an interface. -func (v *SendCLITelemetryResponse) GetSendCLITelemetries() bool { return v.SendCLITelemetries } - -// __SendCLITelemetryInput is used internally by genqlient -type __SendCLITelemetryInput struct { - Telemetry CLITelemetry `json:"telemetry"` -} - -// GetTelemetry returns __SendCLITelemetryInput.Telemetry, and is useful for accessing the field via an interface. -func (v *__SendCLITelemetryInput) GetTelemetry() CLITelemetry { return v.Telemetry } - -// The mutation executed by CreateUserFromAuth0User. -const CreateUserFromAuth0User_Operation = ` -mutation CreateUserFromAuth0User { - me { - registerUser { - ... MeFields - } - } -} -fragment MeFields on Me { - user { - id - email - name - } - organizations { - id - } -} -` - -func CreateUserFromAuth0User( - ctx_ context.Context, - client_ graphql.Client, -) (data_ *CreateUserFromAuth0UserResponse, err_ error) { - req_ := &graphql.Request{ - OpName: "CreateUserFromAuth0User", - Query: CreateUserFromAuth0User_Operation, - } - - data_ = &CreateUserFromAuth0UserResponse{} - resp_ := &graphql.Response{Data: data_} - - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, - ) - - return data_, err_ -} - -// The mutation executed by SendCLITelemetry. -const SendCLITelemetry_Operation = ` -mutation SendCLITelemetry ($telemetry: CLITelemetry!) { - sendCLITelemetries(telemetries: [$telemetry]) -} -` - -func SendCLITelemetry( - ctx_ context.Context, - client_ graphql.Client, - telemetry CLITelemetry, -) (data_ *SendCLITelemetryResponse, err_ error) { - req_ := &graphql.Request{ - OpName: "SendCLITelemetry", - Query: SendCLITelemetry_Operation, - Variables: &__SendCLITelemetryInput{ - Telemetry: telemetry, - }, - } - - data_ = &SendCLITelemetryResponse{} - resp_ := &graphql.Response{Data: data_} - - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, - ) - - return data_, err_ -} diff --git a/src/pkg/cloudclient/graphql/genqlient.graphql b/src/pkg/cloudclient/graphql/genqlient.graphql deleted file mode 100644 index 7abe4d1b..00000000 --- a/src/pkg/cloudclient/graphql/genqlient.graphql +++ /dev/null @@ -1,23 +0,0 @@ -fragment MeFields on Me { - user { - id - email - name - } - organizations { - id - } -} - - -mutation CreateUserFromAuth0User { - me { - registerUser { - ...MeFields - } - } -} - -mutation SendCLITelemetry($telemetry: CLITelemetry!) { - sendCLITelemetries(telemetries: [$telemetry]) -} \ No newline at end of file diff --git a/src/pkg/cloudclient/restapi/resources/clusters.go b/src/pkg/cloudclient/graphql/resources/clusters.go similarity index 50% rename from src/pkg/cloudclient/restapi/resources/clusters.go rename to src/pkg/cloudclient/graphql/resources/clusters.go index 319518bd..261a38d7 100644 --- a/src/pkg/cloudclient/restapi/resources/clusters.go +++ b/src/pkg/cloudclient/graphql/resources/clusters.go @@ -1,41 +1,27 @@ package resources import ( - "context" "fmt" - cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi" - "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/cloudapi" - "github.com/samber/lo" + "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/cloudapi" ) type ClustersResolver struct { - client *cloudclient.Client - clustersByID map[string]cloudapi.Cluster - clustersByName map[string]cloudapi.Cluster + clustersByID map[string]cloudapi.MinimalClusterFields + clustersByName map[string]cloudapi.MinimalClusterFields } -func NewClustersResolver(client *cloudclient.Client) *ClustersResolver { +func NewClustersResolver() *ClustersResolver { return &ClustersResolver{ - client: client, - clustersByID: make(map[string]cloudapi.Cluster), - clustersByName: make(map[string]cloudapi.Cluster), + clustersByID: make(map[string]cloudapi.MinimalClusterFields), + clustersByName: make(map[string]cloudapi.MinimalClusterFields), } } -func (r *ClustersResolver) LoadClusters(ctx context.Context) error { - resp, err := r.client.ClustersQueryWithResponse(ctx, - &cloudapi.ClustersQueryParams{}, - ) - if err != nil { - return err - } - - for _, c := range lo.FromPtr(resp.JSON200) { +func (r *ClustersResolver) LoadClusters(clusters []cloudapi.MinimalClusterFields) { + for _, c := range clusters { r.clustersByID[c.Id] = c r.clustersByName[c.Name] = c } - - return nil } func (r *ClustersResolver) ResolveClusterID(nameOrID string) (string, error) { @@ -61,3 +47,11 @@ func (r *ClustersResolver) ResolveClusterIDs(namesOrIDs []string) ([]string, err } return clusterIDs, nil } + +func (r *ClustersResolver) GetClusterName(clusterID string) (string, error) { + if c, ok := r.clustersByID[clusterID]; ok { + return c.Name, nil + } + + return "", fmt.Errorf("cluster '%s' not found", clusterID) +} diff --git a/src/pkg/cloudclient/restapi/resources/environments.go b/src/pkg/cloudclient/graphql/resources/environments.go similarity index 50% rename from src/pkg/cloudclient/restapi/resources/environments.go rename to src/pkg/cloudclient/graphql/resources/environments.go index ec16547f..6be36eac 100644 --- a/src/pkg/cloudclient/restapi/resources/environments.go +++ b/src/pkg/cloudclient/graphql/resources/environments.go @@ -1,41 +1,27 @@ package resources import ( - "context" "fmt" - cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi" - "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/cloudapi" - "github.com/samber/lo" + "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/cloudapi" ) type EnvironmentsResolver struct { - client *cloudclient.Client - envsByID map[string]cloudapi.Environment - envsByName map[string]cloudapi.Environment + envsByID map[string]cloudapi.MinimalEnvironmentFields + envsByName map[string]cloudapi.MinimalEnvironmentFields } -func NewEnvironmentsResolver(client *cloudclient.Client) *EnvironmentsResolver { +func NewEnvironmentsResolver() *EnvironmentsResolver { return &EnvironmentsResolver{ - client: client, - envsByID: make(map[string]cloudapi.Environment), - envsByName: make(map[string]cloudapi.Environment), + envsByID: make(map[string]cloudapi.MinimalEnvironmentFields), + envsByName: make(map[string]cloudapi.MinimalEnvironmentFields), } } -func (r *EnvironmentsResolver) LoadEnvironments(ctx context.Context) error { - resp, err := r.client.EnvironmentsQueryWithResponse(ctx, - &cloudapi.EnvironmentsQueryParams{}, - ) - if err != nil { - return err - } - - for _, env := range lo.FromPtr(resp.JSON200) { +func (r *EnvironmentsResolver) LoadEnvironments(environments []cloudapi.MinimalEnvironmentFields) { + for _, env := range environments { r.envsByID[env.Id] = env r.envsByName[env.Name] = env } - - return nil } func (r *EnvironmentsResolver) ResolveEnvironmentID(nameOrID string) (string, error) { diff --git a/src/pkg/cloudclient/graphql/resources/namespaces.go b/src/pkg/cloudclient/graphql/resources/namespaces.go new file mode 100644 index 00000000..521777ad --- /dev/null +++ b/src/pkg/cloudclient/graphql/resources/namespaces.go @@ -0,0 +1,103 @@ +package resources + +import ( + "errors" + "fmt" + "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/cloudapi" + "github.com/otterize/otterize-cli/src/pkg/utils/prints" + "strings" +) + +type NamespacesResolver struct { + clusters *ClustersResolver + namespacesByID map[string]cloudapi.MinimalNamespaceFields + namespacesByName map[string][]cloudapi.MinimalNamespaceFields + namespaceByClusterIdAndNamespaceName map[string]map[string]cloudapi.MinimalNamespaceFields +} + +func NewNamespacesResolver(clusters *ClustersResolver) *NamespacesResolver { + return &NamespacesResolver{ + clusters: clusters, + namespacesByID: make(map[string]cloudapi.MinimalNamespaceFields), + namespacesByName: make(map[string][]cloudapi.MinimalNamespaceFields), + namespaceByClusterIdAndNamespaceName: make(map[string]map[string]cloudapi.MinimalNamespaceFields), + } +} + +func (r *NamespacesResolver) LoadNamespaces(namespaces []cloudapi.MinimalNamespaceFields) { + for _, ns := range namespaces { + r.namespacesByID[ns.Id] = ns + r.namespacesByName[ns.Name] = append(r.namespacesByName[ns.Name], ns) + + clusterId := ns.Cluster.Id + + if _, ok := r.namespaceByClusterIdAndNamespaceName[clusterId]; !ok { + r.namespaceByClusterIdAndNamespaceName[clusterId] = make(map[string]cloudapi.MinimalNamespaceFields) + } + r.namespaceByClusterIdAndNamespaceName[clusterId][ns.Name] = ns + } +} + +func (r *NamespacesResolver) errorLogMatchingNamespaces(namespaces []cloudapi.MinimalNamespaceFields) { + prints.PrintCliStderr("The following matching namespaces were found:") + for _, ns := range namespaces { + clusterName, err := r.clusters.GetClusterName(ns.Cluster.Id) + if err != nil { + clusterName = ns.Cluster.Id + } + prints.PrintCliStderr(" - %s.%s (%s)", ns.Name, clusterName, ns.Id) + } +} + +func (r *NamespacesResolver) ResolveNamespaceID(nameOrID string) (string, error) { + if ns, ok := r.namespacesByID[nameOrID]; ok { + return ns.Id, nil + } + + parts := strings.Split(nameOrID, ".") + if len(parts) == 1 { + // namespace + if ns, ok := r.namespacesByName[nameOrID]; ok { + if len(ns) > 1 { + prints.PrintCliStderr("Multiple namespaces found with name '%s'; consider using full namespace name (namespace.cluster)", nameOrID) + r.errorLogMatchingNamespaces(ns) + return "", errors.New("multiple matching namespaces found") + } + return ns[0].Id, nil + } + } else if len(parts) == 2 { + // namespace.cluster + name, cluster := parts[0], parts[1] + clusterId, err := r.clusters.ResolveClusterID(cluster) + if err != nil { + return "", err + } + if ns, ok := r.namespaceByClusterIdAndNamespaceName[clusterId][name]; ok { + return ns.Id, nil + } + } else { + return "", fmt.Errorf("invalid namespace name '%s'", nameOrID) + } + + return "", fmt.Errorf("namespace '%s' not found", nameOrID) +} + +func (r *NamespacesResolver) ResolveNamespaceIDs(namesOrIDs []string) ([]string, error) { + namespaceIDs := make([]string, len(namesOrIDs)) + for i, nameOrID := range namesOrIDs { + namespaceID, err := r.ResolveNamespaceID(nameOrID) + if err != nil { + return nil, err + } + namespaceIDs[i] = namespaceID + } + return namespaceIDs, nil +} + +func (r *NamespacesResolver) GetNamespaceName(namespaceID string) (string, error) { + if c, ok := r.namespacesByID[namespaceID]; ok { + return c.Name, nil + } + + return "", fmt.Errorf("namespace '%s' not found", namespaceID) +} diff --git a/src/pkg/cloudclient/graphql/resources/resolver.go b/src/pkg/cloudclient/graphql/resources/resolver.go new file mode 100644 index 00000000..c06d0eba --- /dev/null +++ b/src/pkg/cloudclient/graphql/resources/resolver.go @@ -0,0 +1,71 @@ +package resources + +import ( + "context" + cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql" +) + +type Resolver struct { + client *cloudclient.Client + orgResources *cloudclient.OrgResources + + clusters *ClustersResolver + environments *EnvironmentsResolver + namespaces *NamespacesResolver + services *ServicesResolver +} + +func NewResolver(client *cloudclient.Client) *Resolver { + environments := NewEnvironmentsResolver() + clusters := NewClustersResolver() + namespaces := NewNamespacesResolver(clusters) + services := NewServicesResolver(client, clusters, namespaces) + + return &Resolver{ + client: client, + environments: environments, + clusters: clusters, + namespaces: namespaces, + services: services, + } +} + +func (r *Resolver) LoadOrgResources(ctx context.Context) error { + resources, err := r.client.LoadOrgResources(ctx) + if err != nil { + return err + } + + r.orgResources = &resources + + r.clusters.LoadClusters(resources.Clusters) + r.environments.LoadEnvironments(resources.Environments) + r.namespaces.LoadNamespaces(resources.Namespaces) + return nil +} + +func (r *Resolver) LoadServices(ctx context.Context) error { + services, err := r.client.ListServices(ctx) + if err != nil { + return err + } + + r.services.LoadServices(services) + return nil +} + +func (r *Resolver) ResolveClusters(clusters []string) ([]string, error) { + return r.clusters.ResolveClusterIDs(clusters) +} + +func (r *Resolver) ResolveEnvironments(environments []string) ([]string, error) { + return r.environments.ResolveEnvironmentIDs(environments) +} + +func (r *Resolver) ResolveNamespaces(namespaces []string) ([]string, error) { + return r.namespaces.ResolveNamespaceIDs(namespaces) +} + +func (r *Resolver) ResolveServices(services []string) ([]string, error) { + return r.services.ResolveServiceIDs(services) +} diff --git a/src/pkg/cloudclient/graphql/resources/services.go b/src/pkg/cloudclient/graphql/resources/services.go new file mode 100644 index 00000000..b63fa3b8 --- /dev/null +++ b/src/pkg/cloudclient/graphql/resources/services.go @@ -0,0 +1,138 @@ +package resources + +import ( + "errors" + "fmt" + cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql" + "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/cloudapi" + "github.com/otterize/otterize-cli/src/pkg/utils/prints" + "github.com/samber/lo" + "strings" +) + +type ServicesResolver struct { + client *cloudclient.Client + clusters *ClustersResolver + namespaces *NamespacesResolver + servicesByID map[string]cloudapi.MinimalServiceFields + servicesByName map[string][]cloudapi.MinimalServiceFields + servicesByNamespaceIdAndServiceName map[string]map[string][]cloudapi.MinimalServiceFields + servicesByClusterIdAndNamespaceIdAndServiceName map[string]map[string]map[string]cloudapi.MinimalServiceFields +} + +func NewServicesResolver(client *cloudclient.Client, clusters *ClustersResolver, namespaces *NamespacesResolver) *ServicesResolver { + return &ServicesResolver{ + client: client, + clusters: clusters, + namespaces: namespaces, + servicesByID: make(map[string]cloudapi.MinimalServiceFields), + servicesByName: make(map[string][]cloudapi.MinimalServiceFields), + servicesByNamespaceIdAndServiceName: make(map[string]map[string][]cloudapi.MinimalServiceFields), + servicesByClusterIdAndNamespaceIdAndServiceName: make(map[string]map[string]map[string]cloudapi.MinimalServiceFields), + } +} + +func (r *ServicesResolver) LoadServices(services []cloudapi.MinimalServiceFields) { + for _, svc := range services { + r.servicesByID[svc.Id] = svc + r.servicesByName[svc.Name] = append(r.servicesByName[svc.Name], svc) + + namespaceId := lo.FromPtr(svc.Namespace).Id + clusterId := lo.FromPtr(svc.Namespace).Cluster.Id + + if _, ok := r.servicesByNamespaceIdAndServiceName[namespaceId]; !ok { + r.servicesByNamespaceIdAndServiceName[namespaceId] = make(map[string][]cloudapi.MinimalServiceFields) + } + r.servicesByNamespaceIdAndServiceName[namespaceId][svc.Name] = append(r.servicesByNamespaceIdAndServiceName[namespaceId][svc.Name], svc) + + if _, ok := r.servicesByClusterIdAndNamespaceIdAndServiceName[clusterId]; !ok { + r.servicesByClusterIdAndNamespaceIdAndServiceName[clusterId] = make(map[string]map[string]cloudapi.MinimalServiceFields) + } + if _, ok := r.servicesByClusterIdAndNamespaceIdAndServiceName[clusterId][namespaceId]; !ok { + r.servicesByClusterIdAndNamespaceIdAndServiceName[clusterId][namespaceId] = make(map[string]cloudapi.MinimalServiceFields) + } + r.servicesByClusterIdAndNamespaceIdAndServiceName[clusterId][namespaceId][svc.Name] = svc + } +} + +func (r *ServicesResolver) errorLogMatchingServices(svcs []cloudapi.MinimalServiceFields) { + prints.PrintCliStderr("The following matching services were found:") + for _, s := range svcs { + namespaceName, err := r.namespaces.GetNamespaceName(s.Namespace.Id) + if err != nil { + namespaceName = s.Namespace.Id + } + clusterName, err := r.clusters.GetClusterName(s.Namespace.Cluster.Id) + if err != nil { + clusterName = s.Namespace.Cluster.Id + } + prints.PrintCliStderr(" - %s.%s.%s (%s)", s.Name, namespaceName, clusterName, s.Id) + } +} + +func (r *ServicesResolver) ResolveServiceID(nameOrID string) (string, error) { + if svc, ok := r.servicesByID[nameOrID]; ok { + return svc.Id, nil + } + + parts := strings.Split(nameOrID, ".") + if len(parts) == 1 { + // service + if svc, ok := r.servicesByName[nameOrID]; ok { + if len(svc) > 1 { + prints.PrintCliStderr("Multiple services found with name '%s'; consider using full service name (service.namespace.cluster)", nameOrID) + r.errorLogMatchingServices(svc) + return "", errors.New("multiple matching services found") + } + return svc[0].Id, nil + } + } else if len(parts) == 2 { + // service.namespace + name, namespace := parts[0], parts[1] + namespaceID, err := r.namespaces.ResolveNamespaceID(namespace) + if err != nil { + return "", err + } + if svc, ok := r.servicesByNamespaceIdAndServiceName[namespaceID][name]; ok { + if len(svc) > 1 { + prints.PrintCliStderr("multiple services found with name '%s'; consider using full service name (service.namespace.cluster)", nameOrID) + r.errorLogMatchingServices(svc) + return "", errors.New("multiple matching services found") + } + return svc[0].Id, nil + } + } else if len(parts) == 3 { + // service.namespace.cluster + name, namespace, cluster := parts[0], parts[1], parts[2] + + clusterId, err := r.clusters.ResolveClusterID(cluster) + if err != nil { + return "", err + } + + fullNamespace := fmt.Sprintf("%s.%s", namespace, cluster) + namespaceId, err := r.namespaces.ResolveNamespaceID(fullNamespace) + if err != nil { + return "", err + } + if svc, ok := r.servicesByClusterIdAndNamespaceIdAndServiceName[clusterId][namespaceId][name]; ok { + return svc.Id, nil + } + } else { + return "", fmt.Errorf("invalid service name '%s'", nameOrID) + } + + return "", fmt.Errorf("service '%s' not found", nameOrID) +} + +func (r *ServicesResolver) ResolveServiceIDs(namesOrIDs []string) ([]string, error) { + serviceIDs := make([]string, len(namesOrIDs)) + for i, nameOrID := range namesOrIDs { + serviceID, err := r.ResolveServiceID(nameOrID) + if err != nil { + return nil, err + } + serviceIDs[i] = serviceID + } + return serviceIDs, nil +} diff --git a/src/pkg/cloudclient/restapi/client.go b/src/pkg/cloudclient/restapi/client.go index b545eb25..b420c7d9 100644 --- a/src/pkg/cloudclient/restapi/client.go +++ b/src/pkg/cloudclient/restapi/client.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/deepmap/oapi-codegen/pkg/securityprovider" "github.com/deepmap/oapi-codegen/pkg/util" + "github.com/google/uuid" "github.com/otterize/otterize-cli/src/pkg/cloudclient/auth" "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/cloudapi" "github.com/otterize/otterize-cli/src/pkg/config" @@ -87,14 +88,17 @@ type ResponseBody struct { } func (d *doerWithErrorCheck) Do(req *http.Request) (*http.Response, error) { + id := uuid.New().String() before := time.Now() - logrus.WithField("method", req.Method).WithField("url", req.URL).Debug("HTTP request") + logrus.WithField("method", req.Method).WithField("url", req.URL). + WithField("id", id). + Debug("HTTP request") resp, err := d.doer.Do(req) after := time.Now() duration := after.Sub(before) logrus.WithField("method", req.Method).WithField("url", req.URL). - WithField("duration", duration). + WithField("id", id).WithField("duration", duration). Debug("HTTP request done") if err != nil { return resp, err diff --git a/src/pkg/cloudclient/restapi/cloudapi/api.gen.go b/src/pkg/cloudclient/restapi/cloudapi/api.gen.go index a9960f8b..7f502785 100644 --- a/src/pkg/cloudclient/restapi/cloudapi/api.gen.go +++ b/src/pkg/cloudclient/restapi/cloudapi/api.gen.go @@ -20,7 +20,6 @@ import ( const ( AccessTokenCookieScopes = "accessTokenCookie.Scopes" - BearerAuthScopes = "bearerAuth.Scopes" Oauth2Scopes = "oauth2.Scopes" OrganizationHeaderScopes = "organizationHeader.Scopes" ) diff --git a/src/pkg/cloudclient/restapi/cloudapi/openapi.json b/src/pkg/cloudclient/restapi/cloudapi/openapi.json index 1692149a..c270d56f 100644 --- a/src/pkg/cloudclient/restapi/cloudapi/openapi.json +++ b/src/pkg/cloudclient/restapi/cloudapi/openapi.json @@ -4393,12 +4393,6 @@ "name": "access_token", "type": "apiKey" }, - "bearerAuth": { - "bearerFormat": "JWT", - "description": "Otterize user JWT token.", - "scheme": "bearer", - "type": "http" - }, "oauth2": { "description": "Use client ID and client secret from an Otterize integration to authenticate.", "flows": { @@ -4421,7 +4415,7 @@ "info": { "title": "Otterize API Server", "version": "v1beta", - "x-revision": "c1aac8f2598857722fe628d9b7816e99bb60c9c3" + "x-revision": "fed83e7133faef5e9b8ed7a801c3fb39b681efaa" }, "openapi": "3.0.0", "paths": { @@ -9684,12 +9678,6 @@ ], "organizationHeader": [ ] - }, - { - "bearerAuth": [ - ], - "organizationHeader": [ - ] } ], "servers": [ diff --git a/src/pkg/cloudclient/restapi/resources/namespaces.go b/src/pkg/cloudclient/restapi/resources/namespaces.go deleted file mode 100644 index e86d72b9..00000000 --- a/src/pkg/cloudclient/restapi/resources/namespaces.go +++ /dev/null @@ -1,99 +0,0 @@ -package resources - -import ( - "context" - "errors" - "fmt" - cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi" - "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/cloudapi" - "github.com/otterize/otterize-cli/src/pkg/utils/prints" - "github.com/samber/lo" - "strings" -) - -type NamespacesResolver struct { - client *cloudclient.Client - namespacesByID map[string]cloudapi.Namespace - namespacesByName map[string][]cloudapi.Namespace - namespaceByClusterAndName map[string]map[string]cloudapi.Namespace -} - -func NewNamespacesResolver(client *cloudclient.Client) *NamespacesResolver { - return &NamespacesResolver{ - client: client, - namespacesByID: make(map[string]cloudapi.Namespace), - namespacesByName: make(map[string][]cloudapi.Namespace), - namespaceByClusterAndName: make(map[string]map[string]cloudapi.Namespace), - } -} - -func (r *NamespacesResolver) LoadNamespaces(ctx context.Context) error { - resp, err := r.client.NamespacesQueryWithResponse(ctx, - &cloudapi.NamespacesQueryParams{}, - ) - if err != nil { - return err - } - - for _, ns := range lo.FromPtr(resp.JSON200) { - r.namespacesByID[ns.Id] = ns - r.namespacesByName[ns.Name] = append(r.namespacesByName[ns.Name], ns) - - cluster := ns.Cluster.Name - - if _, ok := r.namespaceByClusterAndName[cluster]; !ok { - r.namespaceByClusterAndName[cluster] = make(map[string]cloudapi.Namespace) - } - r.namespaceByClusterAndName[cluster][ns.Name] = ns - } - - return nil -} - -func errorLogMatchingNamespaces(namespaces []cloudapi.Namespace) { - prints.PrintCliStderr("The following matching namespaces were found:") - for _, ns := range namespaces { - prints.PrintCliStderr(" - %s.%s (%s)", ns.Name, ns.Cluster.Name, ns.Id) - } -} - -func (r *NamespacesResolver) ResolveNamespaceID(nameOrID string) (string, error) { - if ns, ok := r.namespacesByID[nameOrID]; ok { - return ns.Id, nil - } - - parts := strings.Split(nameOrID, ".") - if len(parts) == 1 { - // namespace - if ns, ok := r.namespacesByName[nameOrID]; ok { - if len(ns) > 1 { - prints.PrintCliStderr("Multiple namespaces found with name '%s'; consider using full namespace name (namespace.cluster)", nameOrID) - errorLogMatchingNamespaces(ns) - return "", errors.New("multiple matching namespaces found") - } - return ns[0].Id, nil - } - } else if len(parts) == 2 { - // namespace.cluster - name, cluster := parts[0], parts[1] - if ns, ok := r.namespaceByClusterAndName[cluster][name]; ok { - return ns.Id, nil - } - } else { - return "", fmt.Errorf("invalid namespace name '%s'", nameOrID) - } - - return "", fmt.Errorf("namespace '%s' not found", nameOrID) -} - -func (r *NamespacesResolver) ResolveNamespaceIDs(namesOrIDs []string) ([]string, error) { - namespaceIDs := make([]string, len(namesOrIDs)) - for i, nameOrID := range namesOrIDs { - namespaceID, err := r.ResolveNamespaceID(nameOrID) - if err != nil { - return nil, err - } - namespaceIDs[i] = namespaceID - } - return namespaceIDs, nil -} diff --git a/src/pkg/cloudclient/restapi/resources/resolver.go b/src/pkg/cloudclient/restapi/resources/resolver.go deleted file mode 100644 index 432a36d1..00000000 --- a/src/pkg/cloudclient/restapi/resources/resolver.go +++ /dev/null @@ -1,144 +0,0 @@ -package resources - -import ( - "context" - cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi" - "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/cloudapi" - "github.com/samber/lo" -) - -type ResolverContext struct { - ctx context.Context - - clusterIDs []string - environmentIDs []string - namespaceIDs []string - serviceIDs []string -} - -type Resolver struct { - client *cloudclient.Client - - clusters *ClustersResolver - environments *EnvironmentsResolver - namespaces *NamespacesResolver - services *ServicesResolver - - context ResolverContext -} - -func NewResolver(client *cloudclient.Client) *Resolver { - return &Resolver{ - client: client, - clusters: NewClustersResolver(client), - environments: NewEnvironmentsResolver(client), - namespaces: NewNamespacesResolver(client), - services: NewServicesResolver(client), - } -} - -func (r *Resolver) WithContext(ctx context.Context) *Resolver { - r.context = ResolverContext{ - ctx: ctx, - } - return r -} - -func (r *Resolver) LoadClusters(clusters []string) error { - if len(clusters) == 0 { - return nil - } - - if err := r.clusters.LoadClusters(r.context.ctx); err != nil { - return err - } - - clusterIDs, err := r.clusters.ResolveClusterIDs(clusters) - if err != nil { - return err - } - - r.context.clusterIDs = clusterIDs - return nil -} - -func (r *Resolver) LoadEnvironments(environments []string) error { - if len(environments) == 0 { - return nil - } - - if err := r.environments.LoadEnvironments(r.context.ctx); err != nil { - return err - } - - environmentIDs, err := r.environments.ResolveEnvironmentIDs(environments) - if err != nil { - return err - } - - r.context.environmentIDs = environmentIDs - return nil -} - -func (r *Resolver) LoadNamespaces(namespaces []string) error { - if len(namespaces) == 0 { - return nil - } - - if err := r.namespaces.LoadNamespaces(r.context.ctx); err != nil { - return err - } - - namespaceIDs, err := r.namespaces.ResolveNamespaceIDs(namespaces) - if err != nil { - return err - } - - r.context.namespaceIDs = namespaceIDs - return nil -} - -func (r *Resolver) LoadServices(services []string) error { - if len(services) == 0 { - return nil - } - - if err := r.services.LoadServices(r.context.ctx); err != nil { - return err - } - - serviceIDs, err := r.services.ResolveServiceIDs(services) - if err != nil { - return err - } - - r.context.serviceIDs = serviceIDs - return nil -} - -func (r *Resolver) BuildServicesFilter() cloudapi.InputServiceFilter { - return cloudapi.InputServiceFilter{ - ClusterIds: lo.EmptyableToPtr(r.context.clusterIDs), - EnvironmentIds: lo.EmptyableToPtr(r.context.environmentIDs), - NamespaceIds: lo.EmptyableToPtr(r.context.namespaceIDs), - ServiceIds: lo.EmptyableToPtr(r.context.serviceIDs), - } -} - -func toIncludeFilterIfNonEmpty(items []string) *map[string]any { - if len(items) == 0 { - return nil - } - return &map[string]any{ - "include": lo.ToPtr(items), - } -} - -func (r *Resolver) BuildAccessGraphFilter() cloudapi.InputAccessGraphFilter { - return cloudapi.InputAccessGraphFilter{ - ClusterIds: toIncludeFilterIfNonEmpty(r.context.clusterIDs), - EnvironmentIds: toIncludeFilterIfNonEmpty(r.context.environmentIDs), - NamespaceIds: toIncludeFilterIfNonEmpty(r.context.namespaceIDs), - ServiceIds: toIncludeFilterIfNonEmpty(r.context.serviceIDs), - } -} diff --git a/src/pkg/cloudclient/restapi/resources/services.go b/src/pkg/cloudclient/restapi/resources/services.go deleted file mode 100644 index 01c9b152..00000000 --- a/src/pkg/cloudclient/restapi/resources/services.go +++ /dev/null @@ -1,121 +0,0 @@ -package resources - -import ( - "context" - "errors" - "fmt" - cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi" - "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/cloudapi" - "github.com/otterize/otterize-cli/src/pkg/utils/prints" - "github.com/samber/lo" - "strings" -) - -type ServicesResolver struct { - client *cloudclient.Client - servicesByID map[string]cloudapi.Service - servicesByName map[string][]cloudapi.Service - servicesByNamespaceName map[string]map[string][]cloudapi.Service - servicesByClusterAndNamespaceName map[string]map[string]map[string]cloudapi.Service -} - -func NewServicesResolver(client *cloudclient.Client) *ServicesResolver { - return &ServicesResolver{ - client: client, - servicesByID: make(map[string]cloudapi.Service), - servicesByName: make(map[string][]cloudapi.Service), - servicesByNamespaceName: make(map[string]map[string][]cloudapi.Service), - servicesByClusterAndNamespaceName: make(map[string]map[string]map[string]cloudapi.Service), - } -} - -func (r *ServicesResolver) LoadServices(ctx context.Context) error { - resp, err := r.client.ServicesQueryWithResponse(ctx, - &cloudapi.ServicesQueryParams{}, - ) - if err != nil { - return err - } - - for _, svc := range lo.FromPtr(resp.JSON200) { - r.servicesByID[svc.Id] = svc - r.servicesByName[svc.Name] = append(r.servicesByName[svc.Name], svc) - - namespace := lo.FromPtr(svc.Namespace).Name - cluster := lo.FromPtr(svc.Namespace).Cluster.Name - - if _, ok := r.servicesByNamespaceName[namespace]; !ok { - r.servicesByNamespaceName[namespace] = make(map[string][]cloudapi.Service) - } - r.servicesByNamespaceName[namespace][svc.Name] = append(r.servicesByNamespaceName[namespace][svc.Name], svc) - - if _, ok := r.servicesByClusterAndNamespaceName[cluster]; !ok { - r.servicesByClusterAndNamespaceName[cluster] = make(map[string]map[string]cloudapi.Service) - } - if _, ok := r.servicesByClusterAndNamespaceName[cluster][namespace]; !ok { - r.servicesByClusterAndNamespaceName[cluster][namespace] = make(map[string]cloudapi.Service) - } - r.servicesByClusterAndNamespaceName[cluster][namespace][svc.Name] = svc - } - - return nil -} - -func errorLogMatchingServices(svcs []cloudapi.Service) { - prints.PrintCliStderr("The following matching services were found:") - for _, s := range svcs { - prints.PrintCliStderr(" - %s.%s.%s (%s)", s.Name, lo.FromPtr(s.Namespace).Name, lo.FromPtr(s.Namespace).Cluster.Name, s.Id) - } -} - -func (r *ServicesResolver) ResolveServiceID(nameOrID string) (string, error) { - if svc, ok := r.servicesByID[nameOrID]; ok { - return svc.Id, nil - } - - parts := strings.Split(nameOrID, ".") - if len(parts) == 1 { - // service - if svc, ok := r.servicesByName[nameOrID]; ok { - if len(svc) > 1 { - prints.PrintCliStderr("Multiple services found with name '%s'; consider using full service name (service.namespace.cluster)", nameOrID) - errorLogMatchingServices(svc) - return "", errors.New("multiple matching services found") - } - return svc[0].Id, nil - } - } else if len(parts) == 2 { - // service.namespace - name, namespace := parts[0], parts[1] - if svc, ok := r.servicesByNamespaceName[namespace][name]; ok { - if len(svc) > 1 { - prints.PrintCliStderr("multiple services found with name '%s'; consider using full service name (service.namespace.cluster)", nameOrID) - errorLogMatchingServices(svc) - return "", errors.New("multiple matching services found") - } - return svc[0].Id, nil - } - } else if len(parts) == 3 { - // service.namespace.cluster - name, namespace, cluster := parts[0], parts[1], parts[2] - if svc, ok := r.servicesByClusterAndNamespaceName[cluster][namespace][name]; ok { - return svc.Id, nil - } - } else { - return "", fmt.Errorf("invalid service name '%s'", nameOrID) - } - - return "", fmt.Errorf("service '%s' not found", nameOrID) -} - -func (r *ServicesResolver) ResolveServiceIDs(namesOrIDs []string) ([]string, error) { - serviceIDs := make([]string, len(namesOrIDs)) - for i, nameOrID := range namesOrIDs { - serviceID, err := r.ResolveServiceID(nameOrID) - if err != nil { - return nil, err - } - serviceIDs[i] = serviceID - } - return serviceIDs, nil -} diff --git a/src/pkg/output/formatters.go b/src/pkg/output/formatters.go index 730dc720..fec9f29a 100644 --- a/src/pkg/output/formatters.go +++ b/src/pkg/output/formatters.go @@ -159,12 +159,11 @@ func FormatClusters(clusters []cloudapi.Cluster) { } func FormatNamespaces(namespaces []cloudapi.Namespace) { - columns := []string{"ID", "NAME", "CLUSTER", "CLUSTER ID", "ENVIRONMENT ID", "SERVICE COUNT"} + columns := []string{"ID", "NAME", "CLUSTER ID", "ENVIRONMENT ID", "SERVICE COUNT"} getColumnData := func(ns cloudapi.Namespace) []map[string]string { return []map[string]string{{ "ID": ns.Id, "NAME": ns.Name, - "CLUSTER": ns.Cluster.Name, "CLUSTER ID": ns.Cluster.Id, "ENVIRONMENT ID": ns.Environment.Id, "SERVICE COUNT": fmt.Sprintf("%d", ns.ServiceCount), @@ -218,7 +217,7 @@ func enumToString(enumStr string) string { } func FormatServices(services []cloudapi.Service) { - columns := []string{"ID", "NAME", "NAMESPACE", "NAMESPACE ID", "ENVIRONMENT ID"} + columns := []string{"ID", "NAME", "NAMESPACE", "NAMESPACE ID", "CLUSTER_ID", "ENVIRONMENT ID"} getColumnData := func(s cloudapi.Service) []map[string]string { serviceColumns := map[string]string{ "ID": s.Id, @@ -229,6 +228,7 @@ func FormatServices(services []cloudapi.Service) { if s.Namespace != nil { serviceColumns["NAMESPACE"] = s.Namespace.Name serviceColumns["NAMESPACE ID"] = s.Namespace.Id + serviceColumns["CLUSTER ID"] = s.Namespace.Cluster.Id } return []map[string]string{serviceColumns} diff --git a/src/pkg/telemetry/telemetrysender/sender.go b/src/pkg/telemetry/telemetrysender/sender.go index f733ab09..4f229973 100644 --- a/src/pkg/telemetry/telemetrysender/sender.go +++ b/src/pkg/telemetry/telemetrysender/sender.go @@ -3,7 +3,7 @@ package telemetrysender import ( "context" genqlientgraphql "github.com/Khan/genqlient/graphql" - cloudgraphql "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql" + cloudgraphql "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/cloudapi" "github.com/otterize/otterize-cli/src/pkg/config" "github.com/spf13/viper" "golang.org/x/sync/errgroup" From 3a073fed5244ba88b771c61cc23ca0f54db4473d Mon Sep 17 00:00:00 2001 From: Amit Lichtenberg Date: Thu, 24 Apr 2025 11:49:05 +0300 Subject: [PATCH 2/7] Organization around clients --- src/cmd/login/login.go | 2 +- src/pkg/cloudclient/graphql/client.go | 67 +++---------------- src/pkg/cloudclient/graphql/httpclient.go | 45 +++++++++++++ .../cloudclient/login/userlogin/userlogin.go | 4 +- src/pkg/cloudclient/restapi/client.go | 9 +-- src/pkg/cloudclient/restapi/utils.go | 24 ------- src/pkg/config/organization.go | 29 ++++++++ 7 files changed, 91 insertions(+), 89 deletions(-) create mode 100644 src/pkg/cloudclient/graphql/httpclient.go create mode 100644 src/pkg/config/organization.go diff --git a/src/cmd/login/login.go b/src/cmd/login/login.go index 847ac140..2045ee7e 100644 --- a/src/cmd/login/login.go +++ b/src/cmd/login/login.go @@ -46,7 +46,7 @@ var LoginCmd = &cobra.Command{ prints.PrintCliStderr("Login completed successfully! logged in as: %s", authResult.Profile["email"]) apiAddress := viper.GetString(config.OtterizeAPIAddressKey) - loginCtx, err := userlogin.NewContext(apiAddress, authResult.AccessToken) + loginCtx, err := userlogin.NewContext(getConfCtxTimeout, apiAddress, authResult.AccessToken) if err != nil { return err } diff --git a/src/pkg/cloudclient/graphql/client.go b/src/pkg/cloudclient/graphql/client.go index 1f943ece..c33b4bd8 100644 --- a/src/pkg/cloudclient/graphql/client.go +++ b/src/pkg/cloudclient/graphql/client.go @@ -1,21 +1,14 @@ package graphql import ( - "bytes" "context" genqlientgraphql "github.com/Khan/genqlient/graphql" - "github.com/google/uuid" "github.com/otterize/otterize-cli/src/pkg/cloudclient/auth" "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/cloudapi" - "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi" "github.com/otterize/otterize-cli/src/pkg/config" "github.com/samber/lo" - "github.com/sirupsen/logrus" "github.com/spf13/viper" "golang.org/x/oauth2" - "io" - "net/http" - "time" ) type Client struct { @@ -24,67 +17,29 @@ type Client struct { } func NewClient(ctx context.Context) (*Client, error) { - orgID, found := restapi.ResolveOrgID() // TODO: move to shared location - if !found { // Shouldn't happen after login - return nil, restapi.ErrNoOrganization - } - - token := auth.GetAPIToken(ctx) - tokenSrc := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) - - return NewClientFromTokenSourceAndOrgID(viper.GetString(config.OtterizeAPIAddressKey), tokenSrc, orgID), nil -} - -func NewClientFromToken(address string, token string) *Client { - oauth2Token := &oauth2.Token{AccessToken: token} - return NewClientFromTokenSource(address, oauth2.StaticTokenSource(oauth2Token)) -} - -type SetOrgHeaderDoer struct { - orgID string - client genqlientgraphql.Doer -} - -func (d *SetOrgHeaderDoer) Do(req *http.Request) (*http.Response, error) { - id := uuid.New().String() - before := time.Now() - body, err := io.ReadAll(req.Body) + orgID, err := config.ResolveOrgID() if err != nil { return nil, err } - logrus.WithField("method", req.Method).WithField("url", req.URL). - WithField("id", id).WithField("req", string(body)). - Debug("GraphQL request") - - req.Body = io.NopCloser(bytes.NewBuffer(body)) - req.Header.Set("X-Otterize-Organization", d.orgID) - res, err := d.client.Do(req) - after := time.Now() - duration := after.Sub(before) - logrus.WithField("method", req.Method).WithField("url", req.URL). - WithField("id", id).WithField("duration", duration). - Debug("GraphQL request done") + token := auth.GetAPIToken(ctx) - return res, err + return NewClientFromToken(ctx, viper.GetString(config.OtterizeAPIAddressKey), token, orgID), nil } -func NewClientFromTokenSourceAndOrgID(address string, tokenSrc oauth2.TokenSource, orgID string) *Client { - address = address + "/graphql/v1beta" - oauth2client := oauth2.NewClient(context.Background(), tokenSrc) - setHeader := SetOrgHeaderDoer{orgID: orgID, client: oauth2client} - - return &Client{ - Address: address, - Client: genqlientgraphql.NewClient(address, &setHeader), - } +func NewClientFromToken(ctx context.Context, address string, token string, organizationID string) *Client { + tokenSrc := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) + return NewClientFromTokenSourceAndOrgID(ctx, address, tokenSrc, organizationID) } -func NewClientFromTokenSource(address string, tokenSrc oauth2.TokenSource) *Client { +func NewClientFromTokenSourceAndOrgID(ctx context.Context, address string, tokenSrc oauth2.TokenSource, orgID string) *Client { address = address + "/graphql/v1beta" + oauth2client := oauth2.NewClient(ctx, tokenSrc) + client := HTTPClientWithSetOrgHeaderDoer{orgID: orgID, client: oauth2client} + return &Client{ Address: address, - Client: genqlientgraphql.NewClient(address, oauth2.NewClient(context.Background(), tokenSrc)), + Client: genqlientgraphql.NewClient(address, &client), } } diff --git a/src/pkg/cloudclient/graphql/httpclient.go b/src/pkg/cloudclient/graphql/httpclient.go new file mode 100644 index 00000000..9ce7fb6f --- /dev/null +++ b/src/pkg/cloudclient/graphql/httpclient.go @@ -0,0 +1,45 @@ +package graphql + +import ( + "bytes" + genqlientgraphql "github.com/Khan/genqlient/graphql" + "github.com/google/uuid" + "github.com/sirupsen/logrus" + "io" + "net/http" + "time" +) + +type HTTPClientWithSetOrgHeaderDoer struct { + orgID string + client genqlientgraphql.Doer +} + +func (d *HTTPClientWithSetOrgHeaderDoer) Do(req *http.Request) (*http.Response, error) { + id := uuid.New().String() + before := time.Now() + + // load body into separate buffer to properly log it + body, err := io.ReadAll(req.Body) + if err != nil { + return nil, err + } + req.Body = io.NopCloser(bytes.NewBuffer(body)) + + logrus.WithField("method", req.Method).WithField("url", req.URL). + WithField("id", id).WithField("req", string(body)). + Debug("GraphQL request") + + if d.orgID != "" { + req.Header.Set("X-Otterize-Organization", d.orgID) + } + res, err := d.client.Do(req) + + after := time.Now() + duration := after.Sub(before) + logrus.WithField("method", req.Method).WithField("url", req.URL). + WithField("id", id).WithField("duration", duration). + Debug("GraphQL request done") + + return res, err +} diff --git a/src/pkg/cloudclient/login/userlogin/userlogin.go b/src/pkg/cloudclient/login/userlogin/userlogin.go index da54fc5e..4f5ffbb9 100644 --- a/src/pkg/cloudclient/login/userlogin/userlogin.go +++ b/src/pkg/cloudclient/login/userlogin/userlogin.go @@ -22,13 +22,13 @@ type LoginContext struct { me *cloudapi.Me } -func NewContext(apiAddress string, accessToken string) (*LoginContext, error) { +func NewContext(ctx context.Context, apiAddress string, accessToken string) (*LoginContext, error) { apiClient, err := restapi.NewClientFromToken(apiAddress, accessToken, "") if err != nil { return nil, err } - gqlClient := graphql.NewClientFromToken(apiAddress, accessToken) + gqlClient := graphql.NewClientFromToken(ctx, apiAddress, accessToken, "") return &LoginContext{apiClient: apiClient, gqlClient: gqlClient}, nil } diff --git a/src/pkg/cloudclient/restapi/client.go b/src/pkg/cloudclient/restapi/client.go index b420c7d9..8031abc7 100644 --- a/src/pkg/cloudclient/restapi/client.go +++ b/src/pkg/cloudclient/restapi/client.go @@ -3,7 +3,6 @@ package restapi import ( "context" "encoding/json" - "errors" "fmt" "github.com/deepmap/oapi-codegen/pkg/securityprovider" "github.com/deepmap/oapi-codegen/pkg/util" @@ -18,8 +17,6 @@ import ( "time" ) -var ErrNoOrganization = errors.New("no organization exists in config or as parameter") - type Client struct { *cloudapi.ClientWithResponses restApiURL string @@ -30,9 +27,9 @@ type Doer interface { } func NewClient(ctx context.Context) (*Client, error) { - orgID, found := ResolveOrgID() - if !found { // Shouldn't happen after login - return nil, ErrNoOrganization + orgID, err := config.ResolveOrgID() + if err != nil { + return nil, err } return NewClientFromToken(viper.GetString(config.OtterizeAPIAddressKey), auth.GetAPIToken(ctx), orgID) } diff --git a/src/pkg/cloudclient/restapi/utils.go b/src/pkg/cloudclient/restapi/utils.go index 5293bf53..974d6d3e 100644 --- a/src/pkg/cloudclient/restapi/utils.go +++ b/src/pkg/cloudclient/restapi/utils.go @@ -2,10 +2,7 @@ package restapi import ( "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/cloudapi" - "github.com/otterize/otterize-cli/src/pkg/config" - "github.com/otterize/otterize-cli/src/pkg/utils/must" "github.com/samber/lo" - "github.com/spf13/viper" ) func LabelsToLabelInput(labels map[string]string) []cloudapi.LabelInput { @@ -23,24 +20,3 @@ func LabelToLabelInput(key string, value string) cloudapi.LabelInput { Value: lo.Ternary(value == "", nil, &value), } } - -func ResolveOrgID() (string, bool) { - if viper.GetString(config.ApiSelectedOrganizationId) != "" { - return viper.GetString(config.ApiSelectedOrganizationId), true - } - - var Config config.Config - loaded, err := config.LoadConfigFile(&Config, config.ApiCredentialsFilename) - must.Must(err) - - if !loaded { - return "", false - } - - if Config.OrganizationId != "" { - return Config.OrganizationId, true - } - - return "", false - -} diff --git a/src/pkg/config/organization.go b/src/pkg/config/organization.go new file mode 100644 index 00000000..e52bbb40 --- /dev/null +++ b/src/pkg/config/organization.go @@ -0,0 +1,29 @@ +package config + +import ( + "github.com/otterize/intents-operator/src/shared/errors" + "github.com/otterize/otterize-cli/src/pkg/utils/must" + "github.com/spf13/viper" +) + +var ErrNoOrganization = errors.New("no organization exists in config or as parameter") + +func ResolveOrgID() (string, error) { + if viper.GetString(ApiSelectedOrganizationId) != "" { + return viper.GetString(ApiSelectedOrganizationId), nil + } + + var c Config + loaded, err := LoadConfigFile(&c, ApiCredentialsFilename) + must.Must(err) + + if !loaded { + return "", ErrNoOrganization + } + + if c.OrganizationId != "" { + return c.OrganizationId, nil + } + + return "", ErrNoOrganization +} From d09b13a52cc0cb26676175de81ab913b5b82167f Mon Sep 17 00:00:00 2001 From: Amit Lichtenberg Date: Thu, 24 Apr 2025 12:00:15 +0300 Subject: [PATCH 3/7] Fixup --- src/pkg/cloudclient/graphql/client.go | 55 +-- .../cloudclient/graphql/cloudapi/generated.go | 326 ------------------ .../graphql/cloudapi/genqlient.graphql | 32 +- .../graphql/cloudapi/graphql.config.yml | 2 - .../cloudclient/graphql/graphql.config.yml | 2 + 5 files changed, 21 insertions(+), 396 deletions(-) delete mode 100644 src/pkg/cloudclient/graphql/cloudapi/graphql.config.yml create mode 100644 src/pkg/cloudclient/graphql/graphql.config.yml diff --git a/src/pkg/cloudclient/graphql/client.go b/src/pkg/cloudclient/graphql/client.go index c33b4bd8..28ca9a47 100644 --- a/src/pkg/cloudclient/graphql/client.go +++ b/src/pkg/cloudclient/graphql/client.go @@ -52,50 +52,6 @@ func (c *Client) RegisterAuth0User(ctx context.Context) (cloudapi.MeFields, erro return createUserResponse.Me.RegisterUser.MeFields, nil } -func (c *Client) ListClusters(ctx context.Context) ([]cloudapi.MinimalClusterFields, error) { - response, err := cloudapi.ListCluster(ctx, c.Client) - if err != nil { - return nil, err - } - - return lo.Map(response.Clusters, func(c cloudapi.ListClusterClustersCluster, _ int) cloudapi.MinimalClusterFields { - return c.MinimalClusterFields - }), nil -} - -func (c *Client) ListNamespaces(ctx context.Context) ([]cloudapi.MinimalNamespaceFields, error) { - response, err := cloudapi.ListNamespaces(ctx, c.Client) - if err != nil { - return nil, err - } - - return lo.Map(response.Namespaces, func(ns cloudapi.ListNamespacesNamespacesNamespace, _ int) cloudapi.MinimalNamespaceFields { - return ns.MinimalNamespaceFields - }), nil -} - -func (c *Client) ListServices(ctx context.Context) ([]cloudapi.MinimalServiceFields, error) { - response, err := cloudapi.ListServices(ctx, c.Client) - if err != nil { - return nil, err - } - - return lo.Map(response.Services, func(s cloudapi.ListServicesServicesService, _ int) cloudapi.MinimalServiceFields { - return s.MinimalServiceFields - }), nil -} - -func (c *Client) ListEnvironments(ctx context.Context) ([]cloudapi.MinimalEnvironmentFields, error) { - response, err := cloudapi.ListEnvironments(ctx, c.Client) - if err != nil { - return nil, err - } - - return lo.Map(response.Environments, func(e cloudapi.ListEnvironmentsEnvironmentsEnvironment, _ int) cloudapi.MinimalEnvironmentFields { - return e.MinimalEnvironmentFields - }), nil -} - type OrgResources struct { Environments []cloudapi.MinimalEnvironmentFields Clusters []cloudapi.MinimalClusterFields @@ -122,3 +78,14 @@ func (c *Client) LoadOrgResources(ctx context.Context) (OrgResources, error) { return r, nil } + +func (c *Client) ListServices(ctx context.Context) ([]cloudapi.MinimalServiceFields, error) { + response, err := cloudapi.ListServices(ctx, c.Client) + if err != nil { + return nil, err + } + + return lo.Map(response.Services, func(s cloudapi.ListServicesServicesService, _ int) cloudapi.MinimalServiceFields { + return s.MinimalServiceFields + }), nil +} diff --git a/src/pkg/cloudclient/graphql/cloudapi/generated.go b/src/pkg/cloudclient/graphql/cloudapi/generated.go index aec8067e..c86d4fad 100644 --- a/src/pkg/cloudclient/graphql/cloudapi/generated.go +++ b/src/pkg/cloudclient/graphql/cloudapi/generated.go @@ -132,223 +132,6 @@ type CreateUserFromAuth0UserResponse struct { // GetMe returns CreateUserFromAuth0UserResponse.Me, and is useful for accessing the field via an interface. func (v *CreateUserFromAuth0UserResponse) GetMe() CreateUserFromAuth0UserMeMeMutation { return v.Me } -// ListClusterClustersCluster includes the requested fields of the GraphQL type Cluster. -type ListClusterClustersCluster struct { - MinimalClusterFields `json:"-"` -} - -// GetId returns ListClusterClustersCluster.Id, and is useful for accessing the field via an interface. -func (v *ListClusterClustersCluster) GetId() string { return v.MinimalClusterFields.Id } - -// GetName returns ListClusterClustersCluster.Name, and is useful for accessing the field via an interface. -func (v *ListClusterClustersCluster) GetName() string { return v.MinimalClusterFields.Name } - -func (v *ListClusterClustersCluster) UnmarshalJSON(b []byte) error { - - if string(b) == "null" { - return nil - } - - var firstPass struct { - *ListClusterClustersCluster - graphql.NoUnmarshalJSON - } - firstPass.ListClusterClustersCluster = v - - err := json.Unmarshal(b, &firstPass) - if err != nil { - return err - } - - err = json.Unmarshal( - b, &v.MinimalClusterFields) - if err != nil { - return err - } - return nil -} - -type __premarshalListClusterClustersCluster struct { - Id string `json:"id"` - - Name string `json:"name"` -} - -func (v *ListClusterClustersCluster) MarshalJSON() ([]byte, error) { - premarshaled, err := v.__premarshalJSON() - if err != nil { - return nil, err - } - return json.Marshal(premarshaled) -} - -func (v *ListClusterClustersCluster) __premarshalJSON() (*__premarshalListClusterClustersCluster, error) { - var retval __premarshalListClusterClustersCluster - - retval.Id = v.MinimalClusterFields.Id - retval.Name = v.MinimalClusterFields.Name - return &retval, nil -} - -// ListClusterResponse is returned by ListCluster on success. -type ListClusterResponse struct { - // List clusters - Clusters []ListClusterClustersCluster `json:"clusters"` -} - -// GetClusters returns ListClusterResponse.Clusters, and is useful for accessing the field via an interface. -func (v *ListClusterResponse) GetClusters() []ListClusterClustersCluster { return v.Clusters } - -// ListEnvironmentsEnvironmentsEnvironment includes the requested fields of the GraphQL type Environment. -type ListEnvironmentsEnvironmentsEnvironment struct { - MinimalEnvironmentFields `json:"-"` -} - -// GetId returns ListEnvironmentsEnvironmentsEnvironment.Id, and is useful for accessing the field via an interface. -func (v *ListEnvironmentsEnvironmentsEnvironment) GetId() string { - return v.MinimalEnvironmentFields.Id -} - -// GetName returns ListEnvironmentsEnvironmentsEnvironment.Name, and is useful for accessing the field via an interface. -func (v *ListEnvironmentsEnvironmentsEnvironment) GetName() string { - return v.MinimalEnvironmentFields.Name -} - -func (v *ListEnvironmentsEnvironmentsEnvironment) UnmarshalJSON(b []byte) error { - - if string(b) == "null" { - return nil - } - - var firstPass struct { - *ListEnvironmentsEnvironmentsEnvironment - graphql.NoUnmarshalJSON - } - firstPass.ListEnvironmentsEnvironmentsEnvironment = v - - err := json.Unmarshal(b, &firstPass) - if err != nil { - return err - } - - err = json.Unmarshal( - b, &v.MinimalEnvironmentFields) - if err != nil { - return err - } - return nil -} - -type __premarshalListEnvironmentsEnvironmentsEnvironment struct { - Id string `json:"id"` - - Name string `json:"name"` -} - -func (v *ListEnvironmentsEnvironmentsEnvironment) MarshalJSON() ([]byte, error) { - premarshaled, err := v.__premarshalJSON() - if err != nil { - return nil, err - } - return json.Marshal(premarshaled) -} - -func (v *ListEnvironmentsEnvironmentsEnvironment) __premarshalJSON() (*__premarshalListEnvironmentsEnvironmentsEnvironment, error) { - var retval __premarshalListEnvironmentsEnvironmentsEnvironment - - retval.Id = v.MinimalEnvironmentFields.Id - retval.Name = v.MinimalEnvironmentFields.Name - return &retval, nil -} - -// ListEnvironmentsResponse is returned by ListEnvironments on success. -type ListEnvironmentsResponse struct { - // List environments - Environments []ListEnvironmentsEnvironmentsEnvironment `json:"environments"` -} - -// GetEnvironments returns ListEnvironmentsResponse.Environments, and is useful for accessing the field via an interface. -func (v *ListEnvironmentsResponse) GetEnvironments() []ListEnvironmentsEnvironmentsEnvironment { - return v.Environments -} - -// ListNamespacesNamespacesNamespace includes the requested fields of the GraphQL type Namespace. -type ListNamespacesNamespacesNamespace struct { - MinimalNamespaceFields `json:"-"` -} - -// GetId returns ListNamespacesNamespacesNamespace.Id, and is useful for accessing the field via an interface. -func (v *ListNamespacesNamespacesNamespace) GetId() string { return v.MinimalNamespaceFields.Id } - -// GetName returns ListNamespacesNamespacesNamespace.Name, and is useful for accessing the field via an interface. -func (v *ListNamespacesNamespacesNamespace) GetName() string { return v.MinimalNamespaceFields.Name } - -// GetCluster returns ListNamespacesNamespacesNamespace.Cluster, and is useful for accessing the field via an interface. -func (v *ListNamespacesNamespacesNamespace) GetCluster() MinimalNamespaceFieldsCluster { - return v.MinimalNamespaceFields.Cluster -} - -func (v *ListNamespacesNamespacesNamespace) UnmarshalJSON(b []byte) error { - - if string(b) == "null" { - return nil - } - - var firstPass struct { - *ListNamespacesNamespacesNamespace - graphql.NoUnmarshalJSON - } - firstPass.ListNamespacesNamespacesNamespace = v - - err := json.Unmarshal(b, &firstPass) - if err != nil { - return err - } - - err = json.Unmarshal( - b, &v.MinimalNamespaceFields) - if err != nil { - return err - } - return nil -} - -type __premarshalListNamespacesNamespacesNamespace struct { - Id string `json:"id"` - - Name string `json:"name"` - - Cluster MinimalNamespaceFieldsCluster `json:"cluster"` -} - -func (v *ListNamespacesNamespacesNamespace) MarshalJSON() ([]byte, error) { - premarshaled, err := v.__premarshalJSON() - if err != nil { - return nil, err - } - return json.Marshal(premarshaled) -} - -func (v *ListNamespacesNamespacesNamespace) __premarshalJSON() (*__premarshalListNamespacesNamespacesNamespace, error) { - var retval __premarshalListNamespacesNamespacesNamespace - - retval.Id = v.MinimalNamespaceFields.Id - retval.Name = v.MinimalNamespaceFields.Name - retval.Cluster = v.MinimalNamespaceFields.Cluster - return &retval, nil -} - -// ListNamespacesResponse is returned by ListNamespaces on success. -type ListNamespacesResponse struct { - // List namespaces - Namespaces []ListNamespacesNamespacesNamespace `json:"namespaces"` -} - -// GetNamespaces returns ListNamespacesResponse.Namespaces, and is useful for accessing the field via an interface. -func (v *ListNamespacesResponse) GetNamespaces() []ListNamespacesNamespacesNamespace { - return v.Namespaces -} - // ListServicesResponse is returned by ListServices on success. type ListServicesResponse struct { // List services @@ -909,115 +692,6 @@ func CreateUserFromAuth0User( return data_, err_ } -// The query executed by ListCluster. -const ListCluster_Operation = ` -query ListCluster { - clusters { - ... MinimalClusterFields - } -} -fragment MinimalClusterFields on Cluster { - id - name -} -` - -func ListCluster( - ctx_ context.Context, - client_ graphql.Client, -) (data_ *ListClusterResponse, err_ error) { - req_ := &graphql.Request{ - OpName: "ListCluster", - Query: ListCluster_Operation, - } - - data_ = &ListClusterResponse{} - resp_ := &graphql.Response{Data: data_} - - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, - ) - - return data_, err_ -} - -// The query executed by ListEnvironments. -const ListEnvironments_Operation = ` -query ListEnvironments { - environments { - ... MinimalEnvironmentFields - } -} -fragment MinimalEnvironmentFields on Environment { - id - name -} -` - -func ListEnvironments( - ctx_ context.Context, - client_ graphql.Client, -) (data_ *ListEnvironmentsResponse, err_ error) { - req_ := &graphql.Request{ - OpName: "ListEnvironments", - Query: ListEnvironments_Operation, - } - - data_ = &ListEnvironmentsResponse{} - resp_ := &graphql.Response{Data: data_} - - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, - ) - - return data_, err_ -} - -// The query executed by ListNamespaces. -const ListNamespaces_Operation = ` -query ListNamespaces { - namespaces { - ... MinimalNamespaceFields - } -} -fragment MinimalNamespaceFields on Namespace { - id - name - cluster { - ... MinimalClusterFields - } -} -fragment MinimalClusterFields on Cluster { - id - name -} -` - -func ListNamespaces( - ctx_ context.Context, - client_ graphql.Client, -) (data_ *ListNamespacesResponse, err_ error) { - req_ := &graphql.Request{ - OpName: "ListNamespaces", - Query: ListNamespaces_Operation, - } - - data_ = &ListNamespacesResponse{} - resp_ := &graphql.Response{Data: data_} - - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, - ) - - return data_, err_ -} - // The query executed by ListServices. const ListServices_Operation = ` query ListServices { diff --git a/src/pkg/cloudclient/graphql/cloudapi/genqlient.graphql b/src/pkg/cloudclient/graphql/cloudapi/genqlient.graphql index e1deea33..4c502490 100644 --- a/src/pkg/cloudclient/graphql/cloudapi/genqlient.graphql +++ b/src/pkg/cloudclient/graphql/cloudapi/genqlient.graphql @@ -27,12 +27,6 @@ fragment MinimalClusterFields on Cluster { name } -query ListCluster { - clusters { - ...MinimalClusterFields - } -} - fragment MinimalNamespaceFields on Namespace { id name @@ -41,12 +35,6 @@ fragment MinimalNamespaceFields on Namespace { } } -query ListNamespaces { - namespaces { - ...MinimalNamespaceFields - } -} - fragment MinimalServiceFields on Service { id name @@ -56,23 +44,12 @@ fragment MinimalServiceFields on Service { } } -query ListServices { - services { - ...MinimalServiceFields - } -} fragment MinimalEnvironmentFields on Environment { id name } -query ListEnvironments { - environments { - ...MinimalEnvironmentFields - } -} - query LoadOrgResources { clusters { ...MinimalClusterFields @@ -83,5 +60,12 @@ query LoadOrgResources { environments { ...MinimalEnvironmentFields } - # services are not pre-loaded as there may be wayy too many + # services are not pre-loaded as there may be wayyy too many +} + + +query ListServices { + services { + ...MinimalServiceFields + } } \ No newline at end of file diff --git a/src/pkg/cloudclient/graphql/cloudapi/graphql.config.yml b/src/pkg/cloudclient/graphql/cloudapi/graphql.config.yml deleted file mode 100644 index 8f323a93..00000000 --- a/src/pkg/cloudclient/graphql/cloudapi/graphql.config.yml +++ /dev/null @@ -1,2 +0,0 @@ -schema: - - ./schema.graphql \ No newline at end of file diff --git a/src/pkg/cloudclient/graphql/graphql.config.yml b/src/pkg/cloudclient/graphql/graphql.config.yml new file mode 100644 index 00000000..38a43ed1 --- /dev/null +++ b/src/pkg/cloudclient/graphql/graphql.config.yml @@ -0,0 +1,2 @@ +schema: + - ./cloudapi/schema.graphql \ No newline at end of file From aeb46e9bc376aa2952606a44f31c1ed4e63a8e68 Mon Sep 17 00:00:00 2001 From: Amit Lichtenberg Date: Thu, 24 Apr 2025 14:17:23 +0300 Subject: [PATCH 4/7] Fixes --- src/cmd/accessgraph/get/get-accessgraph.go | 4 ++-- src/cmd/clientintents/export/export-clientintents.go | 4 ++-- .../graphql/resources => resourcesresolver}/clusters.go | 2 +- .../resources => resourcesresolver}/environments.go | 2 +- .../graphql/resources => resourcesresolver}/namespaces.go | 2 +- .../graphql/resources => resourcesresolver}/resolver.go | 4 ++-- .../graphql/resources => resourcesresolver}/services.go | 7 ++----- 7 files changed, 11 insertions(+), 14 deletions(-) rename src/pkg/{cloudclient/graphql/resources => resourcesresolver}/clusters.go (98%) rename src/pkg/{cloudclient/graphql/resources => resourcesresolver}/environments.go (97%) rename src/pkg/{cloudclient/graphql/resources => resourcesresolver}/namespaces.go (99%) rename src/pkg/{cloudclient/graphql/resources => resourcesresolver}/resolver.go (95%) rename src/pkg/{cloudclient/graphql/resources => resourcesresolver}/services.go (93%) diff --git a/src/cmd/accessgraph/get/get-accessgraph.go b/src/cmd/accessgraph/get/get-accessgraph.go index 64b7b02f..e681f209 100644 --- a/src/cmd/accessgraph/get/get-accessgraph.go +++ b/src/cmd/accessgraph/get/get-accessgraph.go @@ -5,11 +5,11 @@ import ( "fmt" "github.com/otterize/otterize-cli/src/pkg/cli" cloudclientgql "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql" - "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/resources" cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi" "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/cloudapi" "github.com/otterize/otterize-cli/src/pkg/config" "github.com/otterize/otterize-cli/src/pkg/output" + "github.com/otterize/otterize-cli/src/pkg/resourcesresolver" "github.com/otterize/otterize-cli/src/pkg/utils/must" "github.com/samber/lo" "github.com/spf13/cobra" @@ -84,7 +84,7 @@ func accessGraphFilterFromFlags(ctx context.Context) (cloudapi.InputAccessGraphF return cloudapi.InputAccessGraphFilter{}, err } - resolver := resources.NewResolver(gqlClient) + resolver := resourcesresolver.NewResolver(gqlClient) if err := resolver.LoadOrgResources(ctx); err != nil { return cloudapi.InputAccessGraphFilter{}, err } diff --git a/src/cmd/clientintents/export/export-clientintents.go b/src/cmd/clientintents/export/export-clientintents.go index 690278ed..9ff6643c 100644 --- a/src/cmd/clientintents/export/export-clientintents.go +++ b/src/cmd/clientintents/export/export-clientintents.go @@ -5,11 +5,11 @@ import ( "fmt" "github.com/otterize/otterize-cli/src/pkg/cli" cloudclientgql "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql" - "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/resources" cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi" "github.com/otterize/otterize-cli/src/pkg/cloudclient/restapi/cloudapi" "github.com/otterize/otterize-cli/src/pkg/config" "github.com/otterize/otterize-cli/src/pkg/errors" + "github.com/otterize/otterize-cli/src/pkg/resourcesresolver" "github.com/samber/lo" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -99,7 +99,7 @@ func servicesFilterFromFlags(ctx context.Context) (cloudapi.InputServiceFilter, return cloudapi.InputServiceFilter{}, err } - resolver := resources.NewResolver(gqlClient) + resolver := resourcesresolver.NewResolver(gqlClient) if err := resolver.LoadOrgResources(ctx); err != nil { return cloudapi.InputServiceFilter{}, err } diff --git a/src/pkg/cloudclient/graphql/resources/clusters.go b/src/pkg/resourcesresolver/clusters.go similarity index 98% rename from src/pkg/cloudclient/graphql/resources/clusters.go rename to src/pkg/resourcesresolver/clusters.go index 261a38d7..1cb8ab0f 100644 --- a/src/pkg/cloudclient/graphql/resources/clusters.go +++ b/src/pkg/resourcesresolver/clusters.go @@ -1,4 +1,4 @@ -package resources +package resourcesresolver import ( "fmt" diff --git a/src/pkg/cloudclient/graphql/resources/environments.go b/src/pkg/resourcesresolver/environments.go similarity index 97% rename from src/pkg/cloudclient/graphql/resources/environments.go rename to src/pkg/resourcesresolver/environments.go index 6be36eac..f2d0ec51 100644 --- a/src/pkg/cloudclient/graphql/resources/environments.go +++ b/src/pkg/resourcesresolver/environments.go @@ -1,4 +1,4 @@ -package resources +package resourcesresolver import ( "fmt" diff --git a/src/pkg/cloudclient/graphql/resources/namespaces.go b/src/pkg/resourcesresolver/namespaces.go similarity index 99% rename from src/pkg/cloudclient/graphql/resources/namespaces.go rename to src/pkg/resourcesresolver/namespaces.go index 521777ad..50fab998 100644 --- a/src/pkg/cloudclient/graphql/resources/namespaces.go +++ b/src/pkg/resourcesresolver/namespaces.go @@ -1,4 +1,4 @@ -package resources +package resourcesresolver import ( "errors" diff --git a/src/pkg/cloudclient/graphql/resources/resolver.go b/src/pkg/resourcesresolver/resolver.go similarity index 95% rename from src/pkg/cloudclient/graphql/resources/resolver.go rename to src/pkg/resourcesresolver/resolver.go index c06d0eba..7da8257e 100644 --- a/src/pkg/cloudclient/graphql/resources/resolver.go +++ b/src/pkg/resourcesresolver/resolver.go @@ -1,4 +1,4 @@ -package resources +package resourcesresolver import ( "context" @@ -19,7 +19,7 @@ func NewResolver(client *cloudclient.Client) *Resolver { environments := NewEnvironmentsResolver() clusters := NewClustersResolver() namespaces := NewNamespacesResolver(clusters) - services := NewServicesResolver(client, clusters, namespaces) + services := NewServicesResolver(clusters, namespaces) return &Resolver{ client: client, diff --git a/src/pkg/cloudclient/graphql/resources/services.go b/src/pkg/resourcesresolver/services.go similarity index 93% rename from src/pkg/cloudclient/graphql/resources/services.go rename to src/pkg/resourcesresolver/services.go index b63fa3b8..2bc01e77 100644 --- a/src/pkg/cloudclient/graphql/resources/services.go +++ b/src/pkg/resourcesresolver/services.go @@ -1,9 +1,8 @@ -package resources +package resourcesresolver import ( "errors" "fmt" - cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql" "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/cloudapi" "github.com/otterize/otterize-cli/src/pkg/utils/prints" "github.com/samber/lo" @@ -11,7 +10,6 @@ import ( ) type ServicesResolver struct { - client *cloudclient.Client clusters *ClustersResolver namespaces *NamespacesResolver servicesByID map[string]cloudapi.MinimalServiceFields @@ -20,9 +18,8 @@ type ServicesResolver struct { servicesByClusterIdAndNamespaceIdAndServiceName map[string]map[string]map[string]cloudapi.MinimalServiceFields } -func NewServicesResolver(client *cloudclient.Client, clusters *ClustersResolver, namespaces *NamespacesResolver) *ServicesResolver { +func NewServicesResolver(clusters *ClustersResolver, namespaces *NamespacesResolver) *ServicesResolver { return &ServicesResolver{ - client: client, clusters: clusters, namespaces: namespaces, servicesByID: make(map[string]cloudapi.MinimalServiceFields), From 8d6d796435d3e7178d37b245c4668c82178f3336 Mon Sep 17 00:00:00 2001 From: Amit Lichtenberg Date: Thu, 24 Apr 2025 14:21:07 +0300 Subject: [PATCH 5/7] revert changes to formatters --- src/pkg/output/formatters.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pkg/output/formatters.go b/src/pkg/output/formatters.go index fec9f29a..730dc720 100644 --- a/src/pkg/output/formatters.go +++ b/src/pkg/output/formatters.go @@ -159,11 +159,12 @@ func FormatClusters(clusters []cloudapi.Cluster) { } func FormatNamespaces(namespaces []cloudapi.Namespace) { - columns := []string{"ID", "NAME", "CLUSTER ID", "ENVIRONMENT ID", "SERVICE COUNT"} + columns := []string{"ID", "NAME", "CLUSTER", "CLUSTER ID", "ENVIRONMENT ID", "SERVICE COUNT"} getColumnData := func(ns cloudapi.Namespace) []map[string]string { return []map[string]string{{ "ID": ns.Id, "NAME": ns.Name, + "CLUSTER": ns.Cluster.Name, "CLUSTER ID": ns.Cluster.Id, "ENVIRONMENT ID": ns.Environment.Id, "SERVICE COUNT": fmt.Sprintf("%d", ns.ServiceCount), @@ -217,7 +218,7 @@ func enumToString(enumStr string) string { } func FormatServices(services []cloudapi.Service) { - columns := []string{"ID", "NAME", "NAMESPACE", "NAMESPACE ID", "CLUSTER_ID", "ENVIRONMENT ID"} + columns := []string{"ID", "NAME", "NAMESPACE", "NAMESPACE ID", "ENVIRONMENT ID"} getColumnData := func(s cloudapi.Service) []map[string]string { serviceColumns := map[string]string{ "ID": s.Id, @@ -228,7 +229,6 @@ func FormatServices(services []cloudapi.Service) { if s.Namespace != nil { serviceColumns["NAMESPACE"] = s.Namespace.Name serviceColumns["NAMESPACE ID"] = s.Namespace.Id - serviceColumns["CLUSTER ID"] = s.Namespace.Cluster.Id } return []map[string]string{serviceColumns} From d54db5ceac2b181da9b74cff99ef0a66a798048f Mon Sep 17 00:00:00 2001 From: Amit Lichtenberg Date: Thu, 24 Apr 2025 14:27:37 +0300 Subject: [PATCH 6/7] Fixes --- src/pkg/resourcesresolver/clusters.go | 6 +++--- src/pkg/resourcesresolver/namespaces.go | 10 +++++----- src/pkg/resourcesresolver/services.go | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pkg/resourcesresolver/clusters.go b/src/pkg/resourcesresolver/clusters.go index 1cb8ab0f..f316e41a 100644 --- a/src/pkg/resourcesresolver/clusters.go +++ b/src/pkg/resourcesresolver/clusters.go @@ -48,10 +48,10 @@ func (r *ClustersResolver) ResolveClusterIDs(namesOrIDs []string) ([]string, err return clusterIDs, nil } -func (r *ClustersResolver) GetClusterName(clusterID string) (string, error) { +func (r *ClustersResolver) GetClusterName(clusterID string) (string, bool) { if c, ok := r.clustersByID[clusterID]; ok { - return c.Name, nil + return c.Name, true } - return "", fmt.Errorf("cluster '%s' not found", clusterID) + return "", false } diff --git a/src/pkg/resourcesresolver/namespaces.go b/src/pkg/resourcesresolver/namespaces.go index 50fab998..7d251169 100644 --- a/src/pkg/resourcesresolver/namespaces.go +++ b/src/pkg/resourcesresolver/namespaces.go @@ -41,8 +41,8 @@ func (r *NamespacesResolver) LoadNamespaces(namespaces []cloudapi.MinimalNamespa func (r *NamespacesResolver) errorLogMatchingNamespaces(namespaces []cloudapi.MinimalNamespaceFields) { prints.PrintCliStderr("The following matching namespaces were found:") for _, ns := range namespaces { - clusterName, err := r.clusters.GetClusterName(ns.Cluster.Id) - if err != nil { + clusterName, ok := r.clusters.GetClusterName(ns.Cluster.Id) + if !ok { clusterName = ns.Cluster.Id } prints.PrintCliStderr(" - %s.%s (%s)", ns.Name, clusterName, ns.Id) @@ -94,10 +94,10 @@ func (r *NamespacesResolver) ResolveNamespaceIDs(namesOrIDs []string) ([]string, return namespaceIDs, nil } -func (r *NamespacesResolver) GetNamespaceName(namespaceID string) (string, error) { +func (r *NamespacesResolver) GetNamespaceName(namespaceID string) (string, bool) { if c, ok := r.namespacesByID[namespaceID]; ok { - return c.Name, nil + return c.Name, true } - return "", fmt.Errorf("namespace '%s' not found", namespaceID) + return "", false } diff --git a/src/pkg/resourcesresolver/services.go b/src/pkg/resourcesresolver/services.go index 2bc01e77..02cc19da 100644 --- a/src/pkg/resourcesresolver/services.go +++ b/src/pkg/resourcesresolver/services.go @@ -55,12 +55,12 @@ func (r *ServicesResolver) LoadServices(services []cloudapi.MinimalServiceFields func (r *ServicesResolver) errorLogMatchingServices(svcs []cloudapi.MinimalServiceFields) { prints.PrintCliStderr("The following matching services were found:") for _, s := range svcs { - namespaceName, err := r.namespaces.GetNamespaceName(s.Namespace.Id) - if err != nil { + namespaceName, ok := r.namespaces.GetNamespaceName(s.Namespace.Id) + if !ok { namespaceName = s.Namespace.Id } - clusterName, err := r.clusters.GetClusterName(s.Namespace.Cluster.Id) - if err != nil { + clusterName, ok := r.clusters.GetClusterName(s.Namespace.Cluster.Id) + if !ok { clusterName = s.Namespace.Cluster.Id } prints.PrintCliStderr(" - %s.%s.%s (%s)", s.Name, namespaceName, clusterName, s.Id) From 5457f1c315ebaa7f5779c4ca09726c21f87e20bd Mon Sep 17 00:00:00 2001 From: Amit Lichtenberg Date: Thu, 24 Apr 2025 14:30:01 +0300 Subject: [PATCH 7/7] Fixes --- src/pkg/resourcesresolver/resolver.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/pkg/resourcesresolver/resolver.go b/src/pkg/resourcesresolver/resolver.go index 7da8257e..44cea060 100644 --- a/src/pkg/resourcesresolver/resolver.go +++ b/src/pkg/resourcesresolver/resolver.go @@ -6,8 +6,7 @@ import ( ) type Resolver struct { - client *cloudclient.Client - orgResources *cloudclient.OrgResources + client *cloudclient.Client clusters *ClustersResolver environments *EnvironmentsResolver @@ -36,8 +35,6 @@ func (r *Resolver) LoadOrgResources(ctx context.Context) error { return err } - r.orgResources = &resources - r.clusters.LoadClusters(resources.Clusters) r.environments.LoadEnvironments(resources.Environments) r.namespaces.LoadNamespaces(resources.Namespaces)