diff --git a/src/cmd/accessgraph/get/get-accessgraph.go b/src/cmd/accessgraph/get/get-accessgraph.go index 31430ccb..e681f209 100644 --- a/src/cmd/accessgraph/get/get-accessgraph.go +++ b/src/cmd/accessgraph/get/get-accessgraph.go @@ -4,11 +4,12 @@ import ( "context" "fmt" "github.com/otterize/otterize-cli/src/pkg/cli" + cloudclientgql "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql" 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/resourcesresolver" "github.com/otterize/otterize-cli/src/pkg/utils/must" "github.com/samber/lo" "github.com/spf13/cobra" @@ -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 := resourcesresolver.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..9ff6643c 100644 --- a/src/cmd/clientintents/export/export-clientintents.go +++ b/src/cmd/clientintents/export/export-clientintents.go @@ -4,11 +4,12 @@ import ( "context" "fmt" "github.com/otterize/otterize-cli/src/pkg/cli" + cloudclientgql "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql" 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/otterize/otterize-cli/src/pkg/resourcesresolver" "github.com/samber/lo" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -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 := resourcesresolver.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/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 d25c668a..28ca9a47 100644 --- a/src/pkg/cloudclient/graphql/client.go +++ b/src/pkg/cloudclient/graphql/client.go @@ -3,6 +3,11 @@ package graphql import ( "context" genqlientgraphql "github.com/Khan/genqlient/graphql" + "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/config" + "github.com/samber/lo" + "github.com/spf13/viper" "golang.org/x/oauth2" ) @@ -11,24 +16,76 @@ type Client struct { Client genqlientgraphql.Client } -func NewClientFromToken(address string, token string) *Client { - oauth2Token := &oauth2.Token{AccessToken: token} - return NewClient(address, oauth2.StaticTokenSource(oauth2Token)) +func NewClient(ctx context.Context) (*Client, error) { + orgID, err := config.ResolveOrgID() + if err != nil { + return nil, err + } + + token := auth.GetAPIToken(ctx) + + return NewClientFromToken(ctx, viper.GetString(config.OtterizeAPIAddressKey), token, orgID), nil +} + +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 NewClient(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), } } -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 } + +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 +} + +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/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..c86d4fad --- /dev/null +++ b/src/pkg/cloudclient/graphql/cloudapi/generated.go @@ -0,0 +1,824 @@ +// 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 } + +// 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 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..4c502490 --- /dev/null +++ b/src/pkg/cloudclient/graphql/cloudapi/genqlient.graphql @@ -0,0 +1,71 @@ +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 +} + +fragment MinimalNamespaceFields on Namespace { + id + name + cluster { + ...MinimalClusterFields + } +} + +fragment MinimalServiceFields on Service { + id + name + # @genqlient(pointer: true) + namespace { + ...MinimalNamespaceFields + } +} + + +fragment MinimalEnvironmentFields on Environment { + id + name +} + +query LoadOrgResources { + clusters { + ...MinimalClusterFields + } + namespaces { + ...MinimalNamespaceFields + } + environments { + ...MinimalEnvironmentFields + } + # 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/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/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/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 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 b545eb25..8031abc7 100644 --- a/src/pkg/cloudclient/restapi/client.go +++ b/src/pkg/cloudclient/restapi/client.go @@ -3,10 +3,10 @@ package restapi import ( "context" "encoding/json" - "errors" "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" @@ -17,8 +17,6 @@ import ( "time" ) -var ErrNoOrganization = errors.New("no organization exists in config or as parameter") - type Client struct { *cloudapi.ClientWithResponses restApiURL string @@ -29,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) } @@ -87,14 +85,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/clusters.go b/src/pkg/cloudclient/restapi/resources/clusters.go deleted file mode 100644 index 319518bd..00000000 --- a/src/pkg/cloudclient/restapi/resources/clusters.go +++ /dev/null @@ -1,63 +0,0 @@ -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" -) - -type ClustersResolver struct { - client *cloudclient.Client - clustersByID map[string]cloudapi.Cluster - clustersByName map[string]cloudapi.Cluster -} - -func NewClustersResolver(client *cloudclient.Client) *ClustersResolver { - return &ClustersResolver{ - client: client, - clustersByID: make(map[string]cloudapi.Cluster), - clustersByName: make(map[string]cloudapi.Cluster), - } -} - -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) { - r.clustersByID[c.Id] = c - r.clustersByName[c.Name] = c - } - - return nil -} - -func (r *ClustersResolver) ResolveClusterID(nameOrID string) (string, error) { - if c, ok := r.clustersByID[nameOrID]; ok { - return c.Id, nil - } - - if c, ok := r.clustersByName[nameOrID]; ok { - return c.Id, nil - } - - return "", fmt.Errorf("cluster '%s' not found", nameOrID) -} - -func (r *ClustersResolver) ResolveClusterIDs(namesOrIDs []string) ([]string, error) { - clusterIDs := make([]string, len(namesOrIDs)) - for i, nameOrID := range namesOrIDs { - clusterID, err := r.ResolveClusterID(nameOrID) - if err != nil { - return nil, err - } - clusterIDs[i] = clusterID - } - return clusterIDs, nil -} diff --git a/src/pkg/cloudclient/restapi/resources/environments.go b/src/pkg/cloudclient/restapi/resources/environments.go deleted file mode 100644 index ec16547f..00000000 --- a/src/pkg/cloudclient/restapi/resources/environments.go +++ /dev/null @@ -1,63 +0,0 @@ -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" -) - -type EnvironmentsResolver struct { - client *cloudclient.Client - envsByID map[string]cloudapi.Environment - envsByName map[string]cloudapi.Environment -} - -func NewEnvironmentsResolver(client *cloudclient.Client) *EnvironmentsResolver { - return &EnvironmentsResolver{ - client: client, - envsByID: make(map[string]cloudapi.Environment), - envsByName: make(map[string]cloudapi.Environment), - } -} - -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) { - r.envsByID[env.Id] = env - r.envsByName[env.Name] = env - } - - return nil -} - -func (r *EnvironmentsResolver) ResolveEnvironmentID(nameOrID string) (string, error) { - if env, ok := r.envsByID[nameOrID]; ok { - return env.Id, nil - } - - if env, ok := r.envsByName[nameOrID]; ok { - return env.Id, nil - } - - return "", fmt.Errorf("environment '%s' not found", nameOrID) -} - -func (r *EnvironmentsResolver) ResolveEnvironmentIDs(namesOrIDs []string) ([]string, error) { - envIDs := make([]string, len(namesOrIDs)) - for i, nameOrID := range namesOrIDs { - envID, err := r.ResolveEnvironmentID(nameOrID) - if err != nil { - return nil, err - } - envIDs[i] = envID - } - return envIDs, nil -} 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/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 +} diff --git a/src/pkg/resourcesresolver/clusters.go b/src/pkg/resourcesresolver/clusters.go new file mode 100644 index 00000000..f316e41a --- /dev/null +++ b/src/pkg/resourcesresolver/clusters.go @@ -0,0 +1,57 @@ +package resourcesresolver + +import ( + "fmt" + "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/cloudapi" +) + +type ClustersResolver struct { + clustersByID map[string]cloudapi.MinimalClusterFields + clustersByName map[string]cloudapi.MinimalClusterFields +} + +func NewClustersResolver() *ClustersResolver { + return &ClustersResolver{ + clustersByID: make(map[string]cloudapi.MinimalClusterFields), + clustersByName: make(map[string]cloudapi.MinimalClusterFields), + } +} + +func (r *ClustersResolver) LoadClusters(clusters []cloudapi.MinimalClusterFields) { + for _, c := range clusters { + r.clustersByID[c.Id] = c + r.clustersByName[c.Name] = c + } +} + +func (r *ClustersResolver) ResolveClusterID(nameOrID string) (string, error) { + if c, ok := r.clustersByID[nameOrID]; ok { + return c.Id, nil + } + + if c, ok := r.clustersByName[nameOrID]; ok { + return c.Id, nil + } + + return "", fmt.Errorf("cluster '%s' not found", nameOrID) +} + +func (r *ClustersResolver) ResolveClusterIDs(namesOrIDs []string) ([]string, error) { + clusterIDs := make([]string, len(namesOrIDs)) + for i, nameOrID := range namesOrIDs { + clusterID, err := r.ResolveClusterID(nameOrID) + if err != nil { + return nil, err + } + clusterIDs[i] = clusterID + } + return clusterIDs, nil +} + +func (r *ClustersResolver) GetClusterName(clusterID string) (string, bool) { + if c, ok := r.clustersByID[clusterID]; ok { + return c.Name, true + } + + return "", false +} diff --git a/src/pkg/resourcesresolver/environments.go b/src/pkg/resourcesresolver/environments.go new file mode 100644 index 00000000..f2d0ec51 --- /dev/null +++ b/src/pkg/resourcesresolver/environments.go @@ -0,0 +1,49 @@ +package resourcesresolver + +import ( + "fmt" + "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql/cloudapi" +) + +type EnvironmentsResolver struct { + envsByID map[string]cloudapi.MinimalEnvironmentFields + envsByName map[string]cloudapi.MinimalEnvironmentFields +} + +func NewEnvironmentsResolver() *EnvironmentsResolver { + return &EnvironmentsResolver{ + envsByID: make(map[string]cloudapi.MinimalEnvironmentFields), + envsByName: make(map[string]cloudapi.MinimalEnvironmentFields), + } +} + +func (r *EnvironmentsResolver) LoadEnvironments(environments []cloudapi.MinimalEnvironmentFields) { + for _, env := range environments { + r.envsByID[env.Id] = env + r.envsByName[env.Name] = env + } +} + +func (r *EnvironmentsResolver) ResolveEnvironmentID(nameOrID string) (string, error) { + if env, ok := r.envsByID[nameOrID]; ok { + return env.Id, nil + } + + if env, ok := r.envsByName[nameOrID]; ok { + return env.Id, nil + } + + return "", fmt.Errorf("environment '%s' not found", nameOrID) +} + +func (r *EnvironmentsResolver) ResolveEnvironmentIDs(namesOrIDs []string) ([]string, error) { + envIDs := make([]string, len(namesOrIDs)) + for i, nameOrID := range namesOrIDs { + envID, err := r.ResolveEnvironmentID(nameOrID) + if err != nil { + return nil, err + } + envIDs[i] = envID + } + return envIDs, nil +} diff --git a/src/pkg/resourcesresolver/namespaces.go b/src/pkg/resourcesresolver/namespaces.go new file mode 100644 index 00000000..7d251169 --- /dev/null +++ b/src/pkg/resourcesresolver/namespaces.go @@ -0,0 +1,103 @@ +package resourcesresolver + +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, ok := r.clusters.GetClusterName(ns.Cluster.Id) + if !ok { + 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, bool) { + if c, ok := r.namespacesByID[namespaceID]; ok { + return c.Name, true + } + + return "", false +} diff --git a/src/pkg/resourcesresolver/resolver.go b/src/pkg/resourcesresolver/resolver.go new file mode 100644 index 00000000..44cea060 --- /dev/null +++ b/src/pkg/resourcesresolver/resolver.go @@ -0,0 +1,68 @@ +package resourcesresolver + +import ( + "context" + cloudclient "github.com/otterize/otterize-cli/src/pkg/cloudclient/graphql" +) + +type Resolver struct { + client *cloudclient.Client + + clusters *ClustersResolver + environments *EnvironmentsResolver + namespaces *NamespacesResolver + services *ServicesResolver +} + +func NewResolver(client *cloudclient.Client) *Resolver { + environments := NewEnvironmentsResolver() + clusters := NewClustersResolver() + namespaces := NewNamespacesResolver(clusters) + services := NewServicesResolver(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.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/resourcesresolver/services.go b/src/pkg/resourcesresolver/services.go new file mode 100644 index 00000000..02cc19da --- /dev/null +++ b/src/pkg/resourcesresolver/services.go @@ -0,0 +1,135 @@ +package resourcesresolver + +import ( + "errors" + "fmt" + "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 { + 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(clusters *ClustersResolver, namespaces *NamespacesResolver) *ServicesResolver { + return &ServicesResolver{ + 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, ok := r.namespaces.GetNamespaceName(s.Namespace.Id) + if !ok { + namespaceName = s.Namespace.Id + } + 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) + } +} + +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/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"