From 9b2f0b369cdc3a57eb78a8e259c07472eef8d705 Mon Sep 17 00:00:00 2001 From: Abhishek Sah Date: Tue, 17 Feb 2026 15:37:20 +0530 Subject: [PATCH 1/5] refactor(cmd): migrate CLI commands from gRPC to ConnectRPC clients Switch all CLI commands to use ConnectRPC clients instead of gRPC clients. This decouples the CLI from the legacy gRPC server, preparing for its removal. Key changes: - cmd/client.go: Replace gRPC dial with ConnectRPC HTTP client factory - cmd/context.go: Replace gRPC metadata with generic newRequest[T] helper - All command files: Use connect.NewRequest() / newRequest() patterns - Response access: res.GetField() -> res.Msg.GetField() - Remove gRPC connection lifecycle (cancel, defer cancel) Co-Authored-By: Claude Opus 4.6 --- cmd/client.go | 62 ++++++----------------- cmd/context.go | 28 +++++++---- cmd/group.go | 35 ++++++------- cmd/namespace.go | 19 +++---- cmd/organization.go | 43 +++++++--------- cmd/permission.go | 34 ++++++------- cmd/policy.go | 25 ++++----- cmd/preferences.go | 30 ++++------- cmd/project.go | 36 ++++++------- cmd/role.go | 35 ++++++------- cmd/seed.go | 120 ++++++++++++++++++++++---------------------- cmd/user.go | 38 ++++++-------- 12 files changed, 219 insertions(+), 286 deletions(-) diff --git a/cmd/client.go b/cmd/client.go index 2a19dbd9b..0364da0f4 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -1,63 +1,33 @@ package cmd import ( - "context" - "time" + "fmt" + "net/http" + "strings" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" - - frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" + frontierv1beta1connect "github.com/raystack/frontier/proto/v1beta1/frontierv1beta1connect" "github.com/spf13/cobra" - "google.golang.org/grpc" ) -func createConnection(ctx context.Context, host string, caCertFile string) (*grpc.ClientConn, error) { - creds := insecure.NewCredentials() - if caCertFile != "" { - tlsCreds, err := credentials.NewClientTLSFromFile(caCertFile, "") - if err != nil { - return nil, err - } - creds = tlsCreds +func createClient(host string) (frontierv1beta1connect.FrontierServiceClient, error) { + if host == "" { + return nil, ErrClientConfigHostNotFound } - opts := []grpc.DialOption{ - grpc.WithTransportCredentials(creds), - grpc.WithBlock(), - } - return grpc.DialContext(ctx, host, opts...) + return frontierv1beta1connect.NewFrontierServiceClient(http.DefaultClient, ensureHTTPScheme(host)), nil } -func createClient(ctx context.Context, host string) (frontierv1beta1.FrontierServiceClient, func(), error) { - dialTimeoutCtx, dialCancel := context.WithTimeout(ctx, time.Second*2) - conn, err := createConnection(dialTimeoutCtx, host, "") - if err != nil { - dialCancel() - return nil, nil, err - } - cancel := func() { - dialCancel() - conn.Close() +func createAdminClient(host string) (frontierv1beta1connect.AdminServiceClient, error) { + if host == "" { + return nil, ErrClientConfigHostNotFound } - - client := frontierv1beta1.NewFrontierServiceClient(conn) - return client, cancel, nil + return frontierv1beta1connect.NewAdminServiceClient(http.DefaultClient, ensureHTTPScheme(host)), nil } -func createAdminClient(ctx context.Context, host string) (frontierv1beta1.AdminServiceClient, func(), error) { - dialTimeoutCtx, dialCancel := context.WithTimeout(ctx, time.Second*2) - conn, err := createConnection(dialTimeoutCtx, host, "") - if err != nil { - dialCancel() - return nil, nil, err - } - cancel := func() { - dialCancel() - conn.Close() +func ensureHTTPScheme(host string) string { + if strings.HasPrefix(host, "http://") || strings.HasPrefix(host, "https://") { + return host } - - client := frontierv1beta1.NewAdminServiceClient(conn) - return client, cancel, nil + return fmt.Sprintf("http://%s", host) } func isClientCLI(cmd *cobra.Command) bool { diff --git a/cmd/context.go b/cmd/context.go index eec116d68..9fc5f7a6a 100644 --- a/cmd/context.go +++ b/cmd/context.go @@ -1,19 +1,27 @@ package cmd import ( - "context" "strings" - "google.golang.org/grpc/metadata" + "connectrpc.com/connect" ) -func setCtxHeader(ctx context.Context, header string) context.Context { - s := strings.Split(header, ":") - key := s[0] - val := s[1] - - md := metadata.New(map[string]string{key: val}) - ctx = metadata.NewOutgoingContext(ctx, md) +// newRequest creates a connect.Request and optionally sets a header from a "key:value" string. +func newRequest[T any](msg *T, header string) *connect.Request[T] { + req := connect.NewRequest(msg) + if header != "" { + if k, v, ok := parseHeader(header); ok { + req.Header().Set(k, v) + } + } + return req +} - return ctx +// parseHeader splits a "key:value" header string into key and value. +func parseHeader(header string) (string, string, bool) { + parts := strings.SplitN(header, ":", 2) + if len(parts) != 2 { + return "", "", false + } + return parts[0], parts[1], true } diff --git a/cmd/group.go b/cmd/group.go index 00feb2850..5e49c2f86 100644 --- a/cmd/group.go +++ b/cmd/group.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "connectrpc.com/connect" "github.com/MakeNowJust/heredoc" "github.com/raystack/frontier/pkg/file" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" @@ -68,21 +69,20 @@ func createGroupCommand(cliConfig *Config) *cli.Command { return err } - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - res, err := client.CreateGroup(setCtxHeader(cmd.Context(), header), &frontierv1beta1.CreateGroupRequest{ + res, err := client.CreateGroup(cmd.Context(), newRequest(&frontierv1beta1.CreateGroupRequest{ Body: &reqBody, - }) + }, header)) if err != nil { return err } spinner.Stop() - fmt.Printf("successfully created group %s with id %s\n", res.GetGroup().GetName(), res.GetGroup().GetId()) + fmt.Printf("successfully created group %s with id %s\n", res.Msg.GetGroup().GetName(), res.Msg.GetGroup().GetId()) return nil }, } @@ -122,17 +122,16 @@ func editGroupCommand(cliConfig *Config) *cli.Command { return err } - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() groupID := args[0] - _, err = client.UpdateGroup(cmd.Context(), &frontierv1beta1.UpdateGroupRequest{ + _, err = client.UpdateGroup(cmd.Context(), connect.NewRequest(&frontierv1beta1.UpdateGroupRequest{ Id: groupID, Body: &reqBody, - }) + })) if err != nil { return err } @@ -166,25 +165,24 @@ func viewGroupCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() orgID := args[0] groupID := args[1] - res, err := client.GetGroup(cmd.Context(), &frontierv1beta1.GetGroupRequest{ + res, err := client.GetGroup(cmd.Context(), connect.NewRequest(&frontierv1beta1.GetGroupRequest{ Id: groupID, OrgId: orgID, - }) + })) if err != nil { return err } report := [][]string{} - group := res.GetGroup() + group := res.Msg.GetGroup() spinner.Stop() @@ -237,21 +235,20 @@ func listGroupCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - res, err := client.ListOrganizationGroups(cmd.Context(), &frontierv1beta1.ListOrganizationGroupsRequest{ + res, err := client.ListOrganizationGroups(cmd.Context(), connect.NewRequest(&frontierv1beta1.ListOrganizationGroupsRequest{ OrgId: args[0], - }) + })) if err != nil { return err } report := [][]string{} - groups := res.GetGroups() + groups := res.Msg.GetGroups() spinner.Stop() diff --git a/cmd/namespace.go b/cmd/namespace.go index cfd5a91b9..b381060ac 100644 --- a/cmd/namespace.go +++ b/cmd/namespace.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "connectrpc.com/connect" "github.com/MakeNowJust/heredoc" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" "github.com/raystack/salt/cli/printer" @@ -51,23 +52,22 @@ func viewNamespaceCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() namespaceID := args[0] - res, err := client.GetNamespace(cmd.Context(), &frontierv1beta1.GetNamespaceRequest{ + res, err := client.GetNamespace(cmd.Context(), connect.NewRequest(&frontierv1beta1.GetNamespaceRequest{ Id: namespaceID, - }) + })) if err != nil { return err } report := [][]string{} - namespace := res.GetNamespace() + namespace := res.Msg.GetNamespace() spinner.Stop() @@ -80,8 +80,6 @@ func viewNamespaceCommand(cliConfig *Config) *cli.Command { }) printer.Table(os.Stdout, report) - spinner.Stop() - return nil }, } @@ -104,19 +102,18 @@ func listNamespaceCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - res, err := client.ListNamespaces(cmd.Context(), &frontierv1beta1.ListNamespacesRequest{}) + res, err := client.ListNamespaces(cmd.Context(), connect.NewRequest(&frontierv1beta1.ListNamespacesRequest{})) if err != nil { return err } report := [][]string{} - namespaces := res.GetNamespaces() + namespaces := res.Msg.GetNamespaces() spinner.Stop() diff --git a/cmd/organization.go b/cmd/organization.go index af2ba4954..30328762f 100644 --- a/cmd/organization.go +++ b/cmd/organization.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "connectrpc.com/connect" "github.com/MakeNowJust/heredoc" "github.com/raystack/frontier/pkg/file" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" @@ -69,22 +70,20 @@ func createOrganizationCommand(cliConfig *Config) *cli.Command { return err } - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - ctx := setCtxHeader(cmd.Context(), header) - res, err := client.CreateOrganization(ctx, &frontierv1beta1.CreateOrganizationRequest{ + res, err := client.CreateOrganization(cmd.Context(), newRequest(&frontierv1beta1.CreateOrganizationRequest{ Body: &reqBody, - }) + }, header)) if err != nil { return err } spinner.Stop() - fmt.Printf("successfully created organization %s with id %s\n", res.GetOrganization().GetName(), res.GetOrganization().GetId()) + fmt.Printf("successfully created organization %s with id %s\n", res.Msg.GetOrganization().GetName(), res.Msg.GetOrganization().GetId()) return nil }, } @@ -124,17 +123,16 @@ func editOrganizationCommand(cliConfig *Config) *cli.Command { return err } - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() organizationID := args[0] - _, err = client.UpdateOrganization(cmd.Context(), &frontierv1beta1.UpdateOrganizationRequest{ + _, err = client.UpdateOrganization(cmd.Context(), connect.NewRequest(&frontierv1beta1.UpdateOrganizationRequest{ Id: organizationID, Body: &reqBody, - }) + })) if err != nil { return err } @@ -168,23 +166,22 @@ func viewOrganizationCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() organizationID := args[0] - res, err := client.GetOrganization(cmd.Context(), &frontierv1beta1.GetOrganizationRequest{ + res, err := client.GetOrganization(cmd.Context(), connect.NewRequest(&frontierv1beta1.GetOrganizationRequest{ Id: organizationID, - }) + })) if err != nil { return err } report := [][]string{} - organization := res.GetOrganization() + organization := res.Msg.GetOrganization() spinner.Stop() @@ -236,19 +233,18 @@ func listOrganizationCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - res, err := client.ListOrganizations(cmd.Context(), &frontierv1beta1.ListOrganizationsRequest{}) + res, err := client.ListOrganizations(cmd.Context(), connect.NewRequest(&frontierv1beta1.ListOrganizationsRequest{})) if err != nil { return err } report := [][]string{} - organizations := res.GetOrganizations() + organizations := res.Msg.GetOrganizations() spinner.Stop() @@ -290,22 +286,21 @@ func admlistOrganizationCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() organizationID := args[0] - res, err := client.ListOrganizationAdmins(cmd.Context(), &frontierv1beta1.ListOrganizationAdminsRequest{ + res, err := client.ListOrganizationAdmins(cmd.Context(), connect.NewRequest(&frontierv1beta1.ListOrganizationAdminsRequest{ Id: organizationID, - }) + })) if err != nil { return err } report := [][]string{} - admins := res.GetUsers() + admins := res.Msg.GetUsers() spinner.Stop() diff --git a/cmd/permission.go b/cmd/permission.go index d86dd2909..6515409e9 100644 --- a/cmd/permission.go +++ b/cmd/permission.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "connectrpc.com/connect" "github.com/MakeNowJust/heredoc" "github.com/raystack/frontier/pkg/file" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" @@ -68,22 +69,20 @@ func createPermissionCommand(cliConfig *Config) *cli.Command { return err } - client, cancel, err := createAdminClient(cmd.Context(), cliConfig.Host) + client, err := createAdminClient(cliConfig.Host) if err != nil { return err } - defer cancel() - ctx := setCtxHeader(cmd.Context(), header) - res, err := client.CreatePermission(ctx, &frontierv1beta1.CreatePermissionRequest{ + res, err := client.CreatePermission(cmd.Context(), newRequest(&frontierv1beta1.CreatePermissionRequest{ Bodies: []*frontierv1beta1.PermissionRequestBody{&reqBody}, - }) + }, header)) if err != nil { return err } spinner.Stop() - fmt.Printf("successfully created permissions %d\n", len(res.GetPermissions())) + fmt.Printf("successfully created permissions %d\n", len(res.Msg.GetPermissions())) return nil }, } @@ -123,17 +122,16 @@ func editPermissionCommand(cliConfig *Config) *cli.Command { return err } - client, cancel, err := createAdminClient(cmd.Context(), cliConfig.Host) + client, err := createAdminClient(cliConfig.Host) if err != nil { return err } - defer cancel() permissionID := args[0] - _, err = client.UpdatePermission(cmd.Context(), &frontierv1beta1.UpdatePermissionRequest{ + _, err = client.UpdatePermission(cmd.Context(), connect.NewRequest(&frontierv1beta1.UpdatePermissionRequest{ Id: permissionID, Body: &reqBody, - }) + })) if err != nil { return err } @@ -165,23 +163,22 @@ func viewPermissionCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() permissionID := args[0] - res, err := client.GetPermission(cmd.Context(), &frontierv1beta1.GetPermissionRequest{ + res, err := client.GetPermission(cmd.Context(), connect.NewRequest(&frontierv1beta1.GetPermissionRequest{ Id: permissionID, - }) + })) if err != nil { return err } report := [][]string{} - action := res.GetPermission() + action := res.Msg.GetPermission() spinner.Stop() @@ -215,19 +212,18 @@ func listPermissionCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - res, err := client.ListPermissions(cmd.Context(), &frontierv1beta1.ListPermissionsRequest{}) + res, err := client.ListPermissions(cmd.Context(), connect.NewRequest(&frontierv1beta1.ListPermissionsRequest{})) if err != nil { return err } report := [][]string{} - permissions := res.GetPermissions() + permissions := res.Msg.GetPermissions() spinner.Stop() diff --git a/cmd/policy.go b/cmd/policy.go index d457a1074..efd4bb9f3 100644 --- a/cmd/policy.go +++ b/cmd/policy.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "connectrpc.com/connect" "github.com/MakeNowJust/heredoc" "github.com/raystack/frontier/pkg/file" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" @@ -66,16 +67,14 @@ func createPolicyCommand(cliConfig *Config) *cli.Command { return err } - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - ctx := setCtxHeader(cmd.Context(), header) - _, err = client.CreatePolicy(ctx, &frontierv1beta1.CreatePolicyRequest{ + _, err = client.CreatePolicy(cmd.Context(), newRequest(&frontierv1beta1.CreatePolicyRequest{ Body: &reqBody, - }) + }, header)) if err != nil { return err } @@ -121,17 +120,16 @@ func editPolicyCommand(cliConfig *Config) *cli.Command { return err } - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() policyID := args[0] - _, err = client.UpdatePolicy(cmd.Context(), &frontierv1beta1.UpdatePolicyRequest{ + _, err = client.UpdatePolicy(cmd.Context(), connect.NewRequest(&frontierv1beta1.UpdatePolicyRequest{ Id: policyID, Body: &reqBody, - }) + })) if err != nil { return err } @@ -163,23 +161,22 @@ func viewPolicyCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() policyID := args[0] - res, err := client.GetPolicy(cmd.Context(), &frontierv1beta1.GetPolicyRequest{ + res, err := client.GetPolicy(cmd.Context(), connect.NewRequest(&frontierv1beta1.GetPolicyRequest{ Id: policyID, - }) + })) if err != nil { return err } report := [][]string{} - policy := res.GetPolicy() + policy := res.Msg.GetPolicy() spinner.Stop() diff --git a/cmd/preferences.go b/cmd/preferences.go index 3ccdaf723..f36c7e63d 100644 --- a/cmd/preferences.go +++ b/cmd/preferences.go @@ -51,21 +51,17 @@ func preferencesListCommand(cliConfig *Config) *cobra.Command { spinner := printer.Spin("") defer spinner.Stop() - var reqBody frontierv1beta1.ListPreferencesRequest - - adminClient, cancel, err := createAdminClient(cmd.Context(), cliConfig.Host) + adminClient, err := createAdminClient(cliConfig.Host) if err != nil { return err } - defer cancel() - ctx := setCtxHeader(cmd.Context(), header) - res, err := adminClient.ListPreferences(ctx, &reqBody) + res, err := adminClient.ListPreferences(cmd.Context(), newRequest(&frontierv1beta1.ListPreferencesRequest{}, header)) if err != nil { return err } - if len(res.GetPreferences()) == 0 { + if len(res.Msg.GetPreferences()) == 0 { spinner.Stop() fmt.Println("No preferences set") return nil @@ -73,7 +69,7 @@ func preferencesListCommand(cliConfig *Config) *cobra.Command { report := [][]string{} report = append(report, []string{"Name", "Value", "ResourceType", "ResourceID"}) - for _, preference := range res.GetPreferences() { + for _, preference := range res.Msg.GetPreferences() { report = append(report, []string{preference.GetName(), preference.GetValue(), preference.GetResourceType(), preference.GetResourceId()}) } spinner.Stop() @@ -115,20 +111,18 @@ func preferencesSetCommand(cliConfig *Config) *cobra.Command { Value: value, }) - client, cancel, err := createAdminClient(cmd.Context(), cliConfig.Host) + client, err := createAdminClient(cliConfig.Host) if err != nil { return err } - defer cancel() - ctx := setCtxHeader(cmd.Context(), header) - res, err := client.CreatePreferences(ctx, &reqBody) + res, err := client.CreatePreferences(cmd.Context(), newRequest(&reqBody, header)) if err != nil { return err } spinner.Stop() - fmt.Printf("Preference %s set successfully with value: %s \n", res.GetPreference()[0].GetName(), res.GetPreference()[0].GetValue()) + fmt.Printf("Preference %s set successfully with value: %s \n", res.Msg.GetPreference()[0].GetName(), res.Msg.GetPreference()[0].GetValue()) return nil }, @@ -159,22 +153,18 @@ func preferencesGetCommand(cliConfig *Config) *cobra.Command { spinner := printer.Spin("") defer spinner.Stop() - var reqBody frontierv1beta1.DescribePreferencesRequest - - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - ctx := setCtxHeader(cmd.Context(), header) - res, err := client.DescribePreferences(ctx, &reqBody) + res, err := client.DescribePreferences(cmd.Context(), newRequest(&frontierv1beta1.DescribePreferencesRequest{}, header)) if err != nil { return err } spinner.Stop() - fmt.Println(prettyPrint(res.GetTraits())) + fmt.Println(prettyPrint(res.Msg.GetTraits())) return nil }, diff --git a/cmd/project.go b/cmd/project.go index a451bd5b4..ce0c75968 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "connectrpc.com/connect" "github.com/MakeNowJust/heredoc" "github.com/raystack/frontier/pkg/file" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" @@ -68,22 +69,20 @@ func createProjectCommand(cliConfig *Config) *cli.Command { return err } - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - ctx := setCtxHeader(cmd.Context(), header) - res, err := client.CreateProject(ctx, &frontierv1beta1.CreateProjectRequest{ + res, err := client.CreateProject(cmd.Context(), newRequest(&frontierv1beta1.CreateProjectRequest{ Body: &reqBody, - }) + }, header)) if err != nil { return err } spinner.Stop() - fmt.Printf("successfully created project %s with id %s\n", res.GetProject().GetName(), res.GetProject().GetId()) + fmt.Printf("successfully created project %s with id %s\n", res.Msg.GetProject().GetName(), res.Msg.GetProject().GetId()) return nil }, } @@ -123,17 +122,16 @@ func editProjectCommand(cliConfig *Config) *cli.Command { return err } - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() projectID := args[0] - _, err = client.UpdateProject(cmd.Context(), &frontierv1beta1.UpdateProjectRequest{ + _, err = client.UpdateProject(cmd.Context(), connect.NewRequest(&frontierv1beta1.UpdateProjectRequest{ Id: projectID, Body: &reqBody, - }) + })) if err != nil { return err } @@ -167,23 +165,22 @@ func viewProjectCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() projectID := args[0] - res, err := client.GetProject(cmd.Context(), &frontierv1beta1.GetProjectRequest{ + res, err := client.GetProject(cmd.Context(), connect.NewRequest(&frontierv1beta1.GetProjectRequest{ Id: projectID, - }) + })) if err != nil { return err } report := [][]string{} - project := res.GetProject() + project := res.Msg.GetProject() spinner.Stop() @@ -236,21 +233,20 @@ func listProjectCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - res, err := client.ListOrganizationProjects(cmd.Context(), &frontierv1beta1.ListOrganizationProjectsRequest{ + res, err := client.ListOrganizationProjects(cmd.Context(), connect.NewRequest(&frontierv1beta1.ListOrganizationProjectsRequest{ Id: args[0], - }) + })) if err != nil { return err } report := [][]string{} - projects := res.GetProjects() + projects := res.Msg.GetProjects() spinner.Stop() diff --git a/cmd/role.go b/cmd/role.go index 8ee67e542..8f0cd1816 100644 --- a/cmd/role.go +++ b/cmd/role.go @@ -5,6 +5,7 @@ import ( "os" "strings" + "connectrpc.com/connect" "github.com/MakeNowJust/heredoc" "github.com/raystack/frontier/pkg/file" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" @@ -69,23 +70,20 @@ func createRoleCommand(cliConfig *Config) *cli.Command { return err } - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - ctx := setCtxHeader(cmd.Context(), header) - - res, err := client.CreateOrganizationRole(ctx, &frontierv1beta1.CreateOrganizationRoleRequest{ + res, err := client.CreateOrganizationRole(cmd.Context(), newRequest(&frontierv1beta1.CreateOrganizationRoleRequest{ Body: &reqBody, - }) + }, header)) if err != nil { return err } spinner.Stop() - fmt.Printf("successfully created role %s with id %s\n", res.GetRole().GetName(), res.GetRole().GetId()) + fmt.Printf("successfully created role %s with id %s\n", res.Msg.GetRole().GetName(), res.Msg.GetRole().GetId()) return nil }, } @@ -125,17 +123,16 @@ func editRoleCommand(cliConfig *Config) *cli.Command { return err } - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() roleID := args[0] - _, err = client.UpdateOrganizationRole(cmd.Context(), &frontierv1beta1.UpdateOrganizationRoleRequest{ + _, err = client.UpdateOrganizationRole(cmd.Context(), connect.NewRequest(&frontierv1beta1.UpdateOrganizationRoleRequest{ Id: roleID, Body: &reqBody, - }) + })) if err != nil { return err } @@ -169,23 +166,22 @@ func viewRoleCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() roleID := args[0] - res, err := client.GetOrganizationRole(cmd.Context(), &frontierv1beta1.GetOrganizationRoleRequest{ + res, err := client.GetOrganizationRole(cmd.Context(), connect.NewRequest(&frontierv1beta1.GetOrganizationRoleRequest{ Id: roleID, - }) + })) if err != nil { return err } report := [][]string{} - role := res.GetRole() + role := res.Msg.GetRole() spinner.Stop() @@ -239,19 +235,18 @@ func listRoleCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - res, err := client.ListRoles(cmd.Context(), &frontierv1beta1.ListRolesRequest{}) + res, err := client.ListRoles(cmd.Context(), connect.NewRequest(&frontierv1beta1.ListRolesRequest{})) if err != nil { return err } report := [][]string{} - roles := res.GetRoles() + roles := res.Msg.GetRoles() spinner.Stop() diff --git a/cmd/seed.go b/cmd/seed.go index d93bcf521..fdba7c859 100644 --- a/cmd/seed.go +++ b/cmd/seed.go @@ -8,9 +8,11 @@ import ( "fmt" "os" + "connectrpc.com/connect" "github.com/MakeNowJust/heredoc" "github.com/raystack/frontier/config" frontierv1beta1 "github.com/raystack/frontier/proto/v1beta1" + frontierv1beta1connect "github.com/raystack/frontier/proto/v1beta1/frontierv1beta1connect" "github.com/raystack/salt/cli/printer" cli "github.com/spf13/cobra" ) @@ -61,25 +63,23 @@ func SeedCommand(cliConfig *Config) *cli.Command { } header = appConfig.App.IdentityProxyHeader } - header := fmt.Sprintf("%s:%s", header, sampleSeedEmail) - ctx := setCtxHeader(cmd.Context(), header) - adminClient, cancel, err := createAdminClient(ctx, cliConfig.Host) + headerStr := fmt.Sprintf("%s:%s", header, sampleSeedEmail) + + adminClient, err := createAdminClient(cliConfig.Host) if err != nil { return err } - defer cancel() - if err := createCustomRolesAndPermissions(ctx, adminClient); err != nil { + if err := createCustomRolesAndPermissions(cmd.Context(), adminClient, headerStr); err != nil { return fmt.Errorf("failed to create custom permissions: %w", err) } - client, cancel, err := createClient(ctx, cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - if err := bootstrapData(ctx, client); err != nil { + if err := bootstrapData(cmd.Context(), client, headerStr); err != nil { return fmt.Errorf("failed to bootstrap data: %w", err) } fmt.Println("initialized sample data in frontier successfully") @@ -94,15 +94,15 @@ func SeedCommand(cliConfig *Config) *cli.Command { } // create sample platform wide custom permissions and roles -func createCustomRolesAndPermissions(ctx context.Context, client frontierv1beta1.AdminServiceClient) error { +func createCustomRolesAndPermissions(ctx context.Context, client frontierv1beta1connect.AdminServiceClient, header string) error { var permissionBodies []*frontierv1beta1.PermissionRequestBody if err := json.Unmarshal(mockCustomPermissions, &permissionBodies); err != nil { return fmt.Errorf("failed to unmarshal custom permissions: %w", err) } - if _, err := client.CreatePermission(ctx, &frontierv1beta1.CreatePermissionRequest{ + if _, err := client.CreatePermission(ctx, newRequest(&frontierv1beta1.CreatePermissionRequest{ Bodies: permissionBodies, - }); err != nil { + }, header)); err != nil { return fmt.Errorf("failed to create custom permission: %w", err) } @@ -119,15 +119,15 @@ func createCustomRolesAndPermissions(ctx context.Context, client frontierv1beta1 } str = "created custom roles :" - var roleResp *frontierv1beta1.CreateRoleResponse + var roleResp *connect.Response[frontierv1beta1.CreateRoleResponse] var err error for _, role := range roles { - if roleResp, err = client.CreateRole(ctx, &frontierv1beta1.CreateRoleRequest{ + if roleResp, err = client.CreateRole(ctx, newRequest(&frontierv1beta1.CreateRoleRequest{ Body: role, - }); err != nil { + }, header)); err != nil { return fmt.Errorf("failed to create custom role: %w", err) } - roleIDs = append(roleIDs, roleResp.GetRole().GetId()) + roleIDs = append(roleIDs, roleResp.Msg.GetRole().GetId()) str = fmt.Sprintf("%s %s", str, role.GetName()) } @@ -135,7 +135,7 @@ func createCustomRolesAndPermissions(ctx context.Context, client frontierv1beta1 return nil } -func bootstrapData(ctx context.Context, client frontierv1beta1.FrontierServiceClient) error { +func bootstrapData(ctx context.Context, client frontierv1beta1connect.FrontierServiceClient, header string) error { var userBodies []*frontierv1beta1.UserRequestBody if err := json.Unmarshal(mockHumanUser, &userBodies); err != nil { return fmt.Errorf("failed to unmarshal user body: %w", err) @@ -180,107 +180,107 @@ func bootstrapData(ctx context.Context, client frontierv1beta1.FrontierServiceCl reportPolicy = append(reportPolicy, []string{"CREATED_FOR", "ROLE", "RESOURCE"}) for idx, orgBody := range orgBodies { - userResp, err := client.CreateUser(ctx, &frontierv1beta1.CreateUserRequest{ + userResp, err := client.CreateUser(ctx, newRequest(&frontierv1beta1.CreateUserRequest{ Body: userBodies[idx], - }) + }, header)) if err != nil { return fmt.Errorf("failed to create sample user: %w", err) } reportUser = append(reportUser, []string{ - userResp.GetUser().GetId(), - userResp.GetUser().GetName(), - userResp.GetUser().GetEmail(), - userResp.GetUser().GetTitle(), + userResp.Msg.GetUser().GetId(), + userResp.Msg.GetUser().GetName(), + userResp.Msg.GetUser().GetEmail(), + userResp.Msg.GetUser().GetTitle(), }) - orgResp, err := client.CreateOrganization(ctx, &frontierv1beta1.CreateOrganizationRequest{ + orgResp, err := client.CreateOrganization(ctx, newRequest(&frontierv1beta1.CreateOrganizationRequest{ Body: orgBody, - }) + }, header)) if err != nil { return fmt.Errorf("failed to create sample organization: %w", err) } reportOrg = append(reportOrg, []string{ - orgResp.GetOrganization().GetId(), - orgResp.GetOrganization().GetName(), + orgResp.Msg.GetOrganization().GetId(), + orgResp.Msg.GetOrganization().GetName(), sampleSeedEmail, }) // create service user for an org - serviceUserResp, err := client.CreateServiceUser(ctx, &frontierv1beta1.CreateServiceUserRequest{ + serviceUserResp, err := client.CreateServiceUser(ctx, newRequest(&frontierv1beta1.CreateServiceUserRequest{ Body: &frontierv1beta1.ServiceUserRequestBody{Title: "sample service user"}, - OrgId: orgResp.GetOrganization().GetId(), - }) + OrgId: orgResp.Msg.GetOrganization().GetId(), + }, header)) if err != nil { return fmt.Errorf("failed to create sample service user: %w", err) } reportServiceUser = append(reportServiceUser, []string{ - serviceUserResp.GetServiceuser().GetId(), - orgResp.GetOrganization().GetId(), - serviceUserResp.GetServiceuser().GetTitle(), + serviceUserResp.Msg.GetServiceuser().GetId(), + orgResp.Msg.GetOrganization().GetId(), + serviceUserResp.Msg.GetServiceuser().GetTitle(), }) // create service user credentials for an org - serviceUserCredentialResp, err := client.CreateServiceUserCredential(ctx, &frontierv1beta1.CreateServiceUserCredentialRequest{ - Id: serviceUserResp.GetServiceuser().GetId(), + serviceUserCredentialResp, err := client.CreateServiceUserCredential(ctx, newRequest(&frontierv1beta1.CreateServiceUserCredentialRequest{ + Id: serviceUserResp.Msg.GetServiceuser().GetId(), Title: "service user id and pass", - }) + }, header)) if err != nil { return fmt.Errorf("failed to generate sample service user password: %w", err) } reportServiceUserCred = append(reportServiceUserCred, []string{ - serviceUserCredentialResp.GetSecret().GetId(), - serviceUserResp.GetServiceuser().GetId(), - serviceUserCredentialResp.GetSecret().GetSecret(), + serviceUserCredentialResp.Msg.GetSecret().GetId(), + serviceUserResp.Msg.GetServiceuser().GetId(), + serviceUserCredentialResp.Msg.GetSecret().GetSecret(), }) // create project inside org - projBodies[idx].OrgId = orgResp.GetOrganization().GetId() - projResp, err := client.CreateProject(ctx, &frontierv1beta1.CreateProjectRequest{ + projBodies[idx].OrgId = orgResp.Msg.GetOrganization().GetId() + projResp, err := client.CreateProject(ctx, newRequest(&frontierv1beta1.CreateProjectRequest{ Body: projBodies[idx], - }) + }, header)) if err != nil { return fmt.Errorf("failed to create sample project: %w", err) } reportProject = append(reportProject, []string{ - projResp.GetProject().GetId(), - projResp.GetProject().GetName(), - projResp.GetProject().GetTitle(), - orgResp.GetOrganization().GetName(), + projResp.Msg.GetProject().GetId(), + projResp.Msg.GetProject().GetName(), + projResp.Msg.GetProject().GetTitle(), + orgResp.Msg.GetOrganization().GetName(), }) // create resource inside project - resourceBodies[idx].Principal = userResp.GetUser().GetId() - resrcResp, err := client.CreateProjectResource(ctx, &frontierv1beta1.CreateProjectResourceRequest{ - ProjectId: projResp.GetProject().GetId(), + resourceBodies[idx].Principal = userResp.Msg.GetUser().GetId() + resrcResp, err := client.CreateProjectResource(ctx, newRequest(&frontierv1beta1.CreateProjectResourceRequest{ + ProjectId: projResp.Msg.GetProject().GetId(), Body: resourceBodies[idx], - }) + }, header)) if err != nil { return fmt.Errorf("failed to create sample resource: %w", err) } reportResource = append(reportResource, []string{ - resrcResp.GetResource().GetId(), - resrcResp.GetResource().GetName(), - resrcResp.GetResource().GetNamespace(), - projResp.GetProject().GetName(), + resrcResp.Msg.GetResource().GetId(), + resrcResp.Msg.GetResource().GetName(), + resrcResp.Msg.GetResource().GetNamespace(), + projResp.Msg.GetProject().GetName(), }) // create sample policy - resource := fmt.Sprintf("%s:%s", samplePolicyNamespace[idx], resrcResp.GetResource().GetId()) - user := fmt.Sprintf("%s:%s", "app/user", userResp.GetUser().GetId()) - policyResp, err := client.CreatePolicy(ctx, &frontierv1beta1.CreatePolicyRequest{ + resource := fmt.Sprintf("%s:%s", samplePolicyNamespace[idx], resrcResp.Msg.GetResource().GetId()) + user := fmt.Sprintf("%s:%s", "app/user", userResp.Msg.GetUser().GetId()) + policyResp, err := client.CreatePolicy(ctx, newRequest(&frontierv1beta1.CreatePolicyRequest{ Body: &frontierv1beta1.PolicyRequestBody{ RoleId: samplePolicyRole[idx], Resource: resource, Principal: user, Title: "Sample Policy", }, - }) + }, header)) if err != nil { return fmt.Errorf("failed to create sample policy %w", err) } reportPolicy = append(reportPolicy, []string{ - policyResp.GetPolicy().GetPrincipal(), - policyResp.GetPolicy().GetRoleId(), - policyResp.GetPolicy().GetResource(), + policyResp.Msg.GetPolicy().GetPrincipal(), + policyResp.Msg.GetPolicy().GetRoleId(), + policyResp.Msg.GetPolicy().GetResource(), }) } fmt.Printf("\n") diff --git a/cmd/user.go b/cmd/user.go index 178220dbd..7ccf347e9 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -1,10 +1,10 @@ package cmd import ( - "context" "fmt" "os" + "connectrpc.com/connect" "github.com/MakeNowJust/heredoc" "github.com/raystack/frontier/pkg/file" "github.com/raystack/frontier/pkg/str" @@ -74,22 +74,20 @@ func createUserCommand(cliConfig *Config) *cli.Command { reqBody.Name = str.GenerateUserSlug(reqBody.GetEmail()) } - ctx := context.Background() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - res, err := client.CreateUser(setCtxHeader(ctx, header), &frontierv1beta1.CreateUserRequest{ + res, err := client.CreateUser(cmd.Context(), newRequest(&frontierv1beta1.CreateUserRequest{ Body: &reqBody, - }) + }, header)) if err != nil { return err } spinner.Stop() - fmt.Printf("successfully created user %s with id %s\n", res.GetUser().GetName(), res.GetUser().GetId()) + fmt.Printf("successfully created user %s with id %s\n", res.Msg.GetUser().GetName(), res.Msg.GetUser().GetId()) return nil }, } @@ -130,18 +128,16 @@ func editUserCommand(cliConfig *Config) *cli.Command { return err } - ctx := context.Background() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() userID := args[0] - _, err = client.UpdateUser(ctx, &frontierv1beta1.UpdateUserRequest{ + _, err = client.UpdateUser(cmd.Context(), connect.NewRequest(&frontierv1beta1.UpdateUserRequest{ Id: userID, Body: &reqBody, - }) + })) if err != nil { return err } @@ -176,24 +172,22 @@ func viewUserCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - ctx := context.Background() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() userID := args[0] - res, err := client.GetUser(ctx, &frontierv1beta1.GetUserRequest{ + res, err := client.GetUser(cmd.Context(), connect.NewRequest(&frontierv1beta1.GetUserRequest{ Id: userID, - }) + })) if err != nil { return err } report := [][]string{} - user := res.GetUser() + user := res.Msg.GetUser() spinner.Stop() @@ -242,20 +236,18 @@ func listUserCommand(cliConfig *Config) *cli.Command { spinner := printer.Spin("") defer spinner.Stop() - ctx := context.Background() - client, cancel, err := createClient(cmd.Context(), cliConfig.Host) + client, err := createClient(cliConfig.Host) if err != nil { return err } - defer cancel() - res, err := client.ListUsers(ctx, &frontierv1beta1.ListUsersRequest{}) + res, err := client.ListUsers(cmd.Context(), connect.NewRequest(&frontierv1beta1.ListUsersRequest{})) if err != nil { return err } report := [][]string{} - users := res.GetUsers() + users := res.Msg.GetUsers() spinner.Stop() From 8d0f25d6d09f2027a8d07026b0d2880c356be215 Mon Sep 17 00:00:00 2001 From: Abhishek Sah Date: Wed, 18 Feb 2026 10:19:13 +0530 Subject: [PATCH 2/5] fix(cmd): update unit tests for ConnectRPC error handling ConnectRPC wraps connection errors in *connect.Error instead of returning context.DeadlineExceeded directly. Update test assertions to use wantErr flag for connection-failure cases. Co-Authored-By: Claude Opus 4.6 --- cmd/group_test.go | 16 ++++++++++------ cmd/namespace_test.go | 16 ++++++++++------ cmd/organization_test.go | 16 ++++++++++------ cmd/permission_test.go | 16 ++++++++++------ cmd/policy_test.go | 12 ++++++++---- cmd/project_test.go | 16 ++++++++++------ cmd/role_test.go | 16 ++++++++++------ 7 files changed, 68 insertions(+), 40 deletions(-) diff --git a/cmd/group_test.go b/cmd/group_test.go index eed3df8a8..f01c9f599 100644 --- a/cmd/group_test.go +++ b/cmd/group_test.go @@ -2,7 +2,6 @@ package cmd_test import ( "bytes" - "context" "errors" "testing" @@ -21,6 +20,7 @@ func TestClientGroup(t *testing.T) { subCommands []string want string err error + wantErr bool }{ { name: "`group` list only should throw error host not found", @@ -29,10 +29,10 @@ func TestClientGroup(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`group` list with host flag should pass", + name: "`group` list with host flag should return error", want: "", subCommands: []string{"list", orgID, "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, { name: "`group` create only should throw error host not found", @@ -65,10 +65,10 @@ func TestClientGroup(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`group` view with host flag should pass", + name: "`group` view with host flag should return error", want: "", subCommands: []string{"view", orgID, "123", "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, } for _, tt := range tests { @@ -84,7 +84,11 @@ func TestClientGroup(t *testing.T) { err := cli.Execute() got := buf.String() - assert.Equal(t, tt.err, err) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.Equal(t, tt.err, err) + } assert.Equal(t, tt.want, got) }) } diff --git a/cmd/namespace_test.go b/cmd/namespace_test.go index 781780318..fea5dfe00 100644 --- a/cmd/namespace_test.go +++ b/cmd/namespace_test.go @@ -2,7 +2,6 @@ package cmd_test import ( "bytes" - "context" "testing" "github.com/raystack/frontier/cmd" @@ -17,6 +16,7 @@ func TestClientNamespace(t *testing.T) { subCommands []string want string err error + wantErr bool }{ { name: "`namespace` list only should throw error host not found", @@ -25,10 +25,10 @@ func TestClientNamespace(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`namespace` list with host flag should pass", + name: "`namespace` list with host flag should return error", want: "", subCommands: []string{"list", "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, { name: "`namespace` view without host should throw error host not found", @@ -37,10 +37,10 @@ func TestClientNamespace(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`namespace` view with host flag should pass", + name: "`namespace` view with host flag should return error", want: "", subCommands: []string{"view", "123", "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, } for _, tt := range tests { @@ -56,7 +56,11 @@ func TestClientNamespace(t *testing.T) { err := cli.Execute() got := buf.String() - assert.Equal(t, tt.err, err) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.Equal(t, tt.err, err) + } assert.Equal(t, tt.want, got) }) } diff --git a/cmd/organization_test.go b/cmd/organization_test.go index 7a5b116eb..40210a4b5 100644 --- a/cmd/organization_test.go +++ b/cmd/organization_test.go @@ -2,7 +2,6 @@ package cmd_test import ( "bytes" - "context" "errors" "testing" @@ -18,6 +17,7 @@ func TestClientOrganization(t *testing.T) { subCommands []string want string err error + wantErr bool }{ { name: "`organization` list only should throw error host not found", @@ -26,10 +26,10 @@ func TestClientOrganization(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`organization` list with host flag should pass", + name: "`organization` list with host flag should return error", want: "", subCommands: []string{"list", "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, { name: "`organization` create only should throw error host not found", @@ -62,10 +62,10 @@ func TestClientOrganization(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`organization` view with host flag should pass", + name: "`organization` view with host flag should return error", want: "", subCommands: []string{"view", "123", "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, } for _, tt := range tests { @@ -81,7 +81,11 @@ func TestClientOrganization(t *testing.T) { err := cli.Execute() got := buf.String() - assert.Equal(t, tt.err, err) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.Equal(t, tt.err, err) + } assert.Equal(t, tt.want, got) }) } diff --git a/cmd/permission_test.go b/cmd/permission_test.go index 4b5080619..2acab8b59 100644 --- a/cmd/permission_test.go +++ b/cmd/permission_test.go @@ -2,7 +2,6 @@ package cmd_test import ( "bytes" - "context" "errors" "testing" @@ -18,6 +17,7 @@ func TestClientAction(t *testing.T) { subCommands []string want string err error + wantErr bool }{ { name: "`permission` list only should throw error host not found", @@ -26,10 +26,10 @@ func TestClientAction(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`permission` list with host flag should pass", + name: "`permission` list with host flag should return error", want: "", subCommands: []string{"list", "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, { name: "`permission` create only should throw error host not found", @@ -62,10 +62,10 @@ func TestClientAction(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`permission` view with host flag should pass", + name: "`permission` view with host flag should return error", want: "", subCommands: []string{"view", "123", "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, } for _, tt := range tests { @@ -81,7 +81,11 @@ func TestClientAction(t *testing.T) { err := cli.Execute() got := buf.String() - assert.Equal(t, tt.err, err) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.Equal(t, tt.err, err) + } assert.Equal(t, tt.want, got) }) } diff --git a/cmd/policy_test.go b/cmd/policy_test.go index 7dc86e87c..009620d27 100644 --- a/cmd/policy_test.go +++ b/cmd/policy_test.go @@ -2,7 +2,6 @@ package cmd_test import ( "bytes" - "context" "errors" "testing" @@ -18,6 +17,7 @@ func TestClientPolicy(t *testing.T) { subCommands []string want string err error + wantErr bool }{ { name: "`policy` create only should throw error host not found", @@ -50,10 +50,10 @@ func TestClientPolicy(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`policy` view with host flag should pass", + name: "`policy` view with host flag should return error", want: "", subCommands: []string{"view", "123", "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, } for _, tt := range tests { @@ -69,7 +69,11 @@ func TestClientPolicy(t *testing.T) { err := cli.Execute() got := buf.String() - assert.Equal(t, tt.err, err) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.Equal(t, tt.err, err) + } assert.Equal(t, tt.want, got) }) } diff --git a/cmd/project_test.go b/cmd/project_test.go index 13ed7781d..2bee87315 100644 --- a/cmd/project_test.go +++ b/cmd/project_test.go @@ -2,7 +2,6 @@ package cmd_test import ( "bytes" - "context" "errors" "testing" @@ -21,6 +20,7 @@ func TestClientProject(t *testing.T) { subCommands []string want string err error + wantErr bool }{ { name: "`project` list only should throw error host not found", @@ -29,10 +29,10 @@ func TestClientProject(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`project` list with host flag should pass", + name: "`project` list with host flag should return error", want: "", subCommands: []string{"list", orgID, "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, { name: "`project` create only should throw error host not found", @@ -65,10 +65,10 @@ func TestClientProject(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`project` view with host flag should pass", + name: "`project` view with host flag should return error", want: "", subCommands: []string{"view", "123", "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, } for _, tt := range tests { @@ -84,7 +84,11 @@ func TestClientProject(t *testing.T) { err := cli.Execute() got := buf.String() - assert.Equal(t, tt.err, err) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.Equal(t, tt.err, err) + } assert.Equal(t, tt.want, got) }) } diff --git a/cmd/role_test.go b/cmd/role_test.go index ed1983411..97b3d99f2 100644 --- a/cmd/role_test.go +++ b/cmd/role_test.go @@ -2,7 +2,6 @@ package cmd_test import ( "bytes" - "context" "errors" "testing" @@ -18,6 +17,7 @@ func TestClientRole(t *testing.T) { subCommands []string want string err error + wantErr bool }{ { name: "`role` list only should throw error host not found", @@ -26,10 +26,10 @@ func TestClientRole(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`role` list with host flag should pass", + name: "`role` list with host flag should return error", want: "", subCommands: []string{"list", "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, { name: "`role` create only should throw error host not found", @@ -62,10 +62,10 @@ func TestClientRole(t *testing.T) { err: cmd.ErrClientConfigHostNotFound, }, { - name: "`role` view with host flag should pass", + name: "`role` view with host flag should return error", want: "", subCommands: []string{"view", "123", "-h", "test"}, - err: context.DeadlineExceeded, + wantErr: true, }, } for _, tt := range tests { @@ -81,7 +81,11 @@ func TestClientRole(t *testing.T) { err := cli.Execute() got := buf.String() - assert.Equal(t, tt.err, err) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.Equal(t, tt.err, err) + } assert.Equal(t, tt.want, got) }) } From 67a720fb76583e6f8af31886ef20f9ef93ce0263 Mon Sep 17 00:00:00 2001 From: Abhishek Sah Date: Wed, 18 Feb 2026 14:22:23 +0530 Subject: [PATCH 3/5] fix(cmd): default to HTTPS when no scheme is provided Avoids sending auth headers over unencrypted HTTP by default. Users can still explicitly use http:// for local development. Co-Authored-By: Claude Opus 4.6 --- cmd/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/client.go b/cmd/client.go index 0364da0f4..9bd503f47 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -27,7 +27,7 @@ func ensureHTTPScheme(host string) string { if strings.HasPrefix(host, "http://") || strings.HasPrefix(host, "https://") { return host } - return fmt.Sprintf("http://%s", host) + return fmt.Sprintf("https://%s", host) } func isClientCLI(cmd *cobra.Command) bool { From 390ae525eecafaca21e1b165c435075098f04f96 Mon Sep 17 00:00:00 2001 From: Abhishek Sah Date: Wed, 18 Feb 2026 14:28:13 +0530 Subject: [PATCH 4/5] fix(cmd): return error on malformed header instead of silently ignoring parseHeader and newRequest now return errors when the header string is invalid (missing colon separator), preventing silent failures when users pass malformed --header values. Co-Authored-By: Claude Opus 4.6 --- cmd/context.go | 19 +++++++----- cmd/group.go | 8 +++-- cmd/organization.go | 8 +++-- cmd/permission.go | 8 +++-- cmd/policy.go | 8 +++-- cmd/preferences.go | 18 +++++++++-- cmd/project.go | 8 +++-- cmd/role.go | 8 +++-- cmd/seed.go | 73 +++++++++++++++++++++++++++++++++------------ cmd/user.go | 8 +++-- 10 files changed, 122 insertions(+), 44 deletions(-) diff --git a/cmd/context.go b/cmd/context.go index 9fc5f7a6a..8aa53629d 100644 --- a/cmd/context.go +++ b/cmd/context.go @@ -1,27 +1,30 @@ package cmd import ( + "fmt" "strings" "connectrpc.com/connect" ) -// newRequest creates a connect.Request and optionally sets a header from a "key:value" string. -func newRequest[T any](msg *T, header string) *connect.Request[T] { +// newRequest creates a connect.Request and sets a header from a "key:value" string. +func newRequest[T any](msg *T, header string) (*connect.Request[T], error) { req := connect.NewRequest(msg) if header != "" { - if k, v, ok := parseHeader(header); ok { - req.Header().Set(k, v) + k, v, err := parseHeader(header) + if err != nil { + return nil, err } + req.Header().Set(k, v) } - return req + return req, nil } // parseHeader splits a "key:value" header string into key and value. -func parseHeader(header string) (string, string, bool) { +func parseHeader(header string) (string, string, error) { parts := strings.SplitN(header, ":", 2) if len(parts) != 2 { - return "", "", false + return "", "", fmt.Errorf("invalid header format %q: expected key:value", header) } - return parts[0], parts[1], true + return parts[0], parts[1], nil } diff --git a/cmd/group.go b/cmd/group.go index 5e49c2f86..ef2359e51 100644 --- a/cmd/group.go +++ b/cmd/group.go @@ -74,9 +74,13 @@ func createGroupCommand(cliConfig *Config) *cli.Command { return err } - res, err := client.CreateGroup(cmd.Context(), newRequest(&frontierv1beta1.CreateGroupRequest{ + req, err := newRequest(&frontierv1beta1.CreateGroupRequest{ Body: &reqBody, - }, header)) + }, header) + if err != nil { + return err + } + res, err := client.CreateGroup(cmd.Context(), req) if err != nil { return err } diff --git a/cmd/organization.go b/cmd/organization.go index 30328762f..f6cd2ef61 100644 --- a/cmd/organization.go +++ b/cmd/organization.go @@ -75,9 +75,13 @@ func createOrganizationCommand(cliConfig *Config) *cli.Command { return err } - res, err := client.CreateOrganization(cmd.Context(), newRequest(&frontierv1beta1.CreateOrganizationRequest{ + req, err := newRequest(&frontierv1beta1.CreateOrganizationRequest{ Body: &reqBody, - }, header)) + }, header) + if err != nil { + return err + } + res, err := client.CreateOrganization(cmd.Context(), req) if err != nil { return err } diff --git a/cmd/permission.go b/cmd/permission.go index 6515409e9..edc834cc2 100644 --- a/cmd/permission.go +++ b/cmd/permission.go @@ -74,9 +74,13 @@ func createPermissionCommand(cliConfig *Config) *cli.Command { return err } - res, err := client.CreatePermission(cmd.Context(), newRequest(&frontierv1beta1.CreatePermissionRequest{ + req, err := newRequest(&frontierv1beta1.CreatePermissionRequest{ Bodies: []*frontierv1beta1.PermissionRequestBody{&reqBody}, - }, header)) + }, header) + if err != nil { + return err + } + res, err := client.CreatePermission(cmd.Context(), req) if err != nil { return err } diff --git a/cmd/policy.go b/cmd/policy.go index efd4bb9f3..02c659c0d 100644 --- a/cmd/policy.go +++ b/cmd/policy.go @@ -72,9 +72,13 @@ func createPolicyCommand(cliConfig *Config) *cli.Command { return err } - _, err = client.CreatePolicy(cmd.Context(), newRequest(&frontierv1beta1.CreatePolicyRequest{ + req, err := newRequest(&frontierv1beta1.CreatePolicyRequest{ Body: &reqBody, - }, header)) + }, header) + if err != nil { + return err + } + _, err = client.CreatePolicy(cmd.Context(), req) if err != nil { return err } diff --git a/cmd/preferences.go b/cmd/preferences.go index f36c7e63d..04a0abaa5 100644 --- a/cmd/preferences.go +++ b/cmd/preferences.go @@ -56,7 +56,11 @@ func preferencesListCommand(cliConfig *Config) *cobra.Command { return err } - res, err := adminClient.ListPreferences(cmd.Context(), newRequest(&frontierv1beta1.ListPreferencesRequest{}, header)) + req, err := newRequest(&frontierv1beta1.ListPreferencesRequest{}, header) + if err != nil { + return err + } + res, err := adminClient.ListPreferences(cmd.Context(), req) if err != nil { return err } @@ -116,7 +120,11 @@ func preferencesSetCommand(cliConfig *Config) *cobra.Command { return err } - res, err := client.CreatePreferences(cmd.Context(), newRequest(&reqBody, header)) + req, err := newRequest(&reqBody, header) + if err != nil { + return err + } + res, err := client.CreatePreferences(cmd.Context(), req) if err != nil { return err } @@ -158,7 +166,11 @@ func preferencesGetCommand(cliConfig *Config) *cobra.Command { return err } - res, err := client.DescribePreferences(cmd.Context(), newRequest(&frontierv1beta1.DescribePreferencesRequest{}, header)) + req, err := newRequest(&frontierv1beta1.DescribePreferencesRequest{}, header) + if err != nil { + return err + } + res, err := client.DescribePreferences(cmd.Context(), req) if err != nil { return err } diff --git a/cmd/project.go b/cmd/project.go index ce0c75968..b7c72980e 100644 --- a/cmd/project.go +++ b/cmd/project.go @@ -74,9 +74,13 @@ func createProjectCommand(cliConfig *Config) *cli.Command { return err } - res, err := client.CreateProject(cmd.Context(), newRequest(&frontierv1beta1.CreateProjectRequest{ + req, err := newRequest(&frontierv1beta1.CreateProjectRequest{ Body: &reqBody, - }, header)) + }, header) + if err != nil { + return err + } + res, err := client.CreateProject(cmd.Context(), req) if err != nil { return err } diff --git a/cmd/role.go b/cmd/role.go index 8f0cd1816..d6771f205 100644 --- a/cmd/role.go +++ b/cmd/role.go @@ -75,9 +75,13 @@ func createRoleCommand(cliConfig *Config) *cli.Command { return err } - res, err := client.CreateOrganizationRole(cmd.Context(), newRequest(&frontierv1beta1.CreateOrganizationRoleRequest{ + req, err := newRequest(&frontierv1beta1.CreateOrganizationRoleRequest{ Body: &reqBody, - }, header)) + }, header) + if err != nil { + return err + } + res, err := client.CreateOrganizationRole(cmd.Context(), req) if err != nil { return err } diff --git a/cmd/seed.go b/cmd/seed.go index fdba7c859..c30ea73e9 100644 --- a/cmd/seed.go +++ b/cmd/seed.go @@ -100,9 +100,13 @@ func createCustomRolesAndPermissions(ctx context.Context, client frontierv1beta1 return fmt.Errorf("failed to unmarshal custom permissions: %w", err) } - if _, err := client.CreatePermission(ctx, newRequest(&frontierv1beta1.CreatePermissionRequest{ + req, err := newRequest(&frontierv1beta1.CreatePermissionRequest{ Bodies: permissionBodies, - }, header)); err != nil { + }, header) + if err != nil { + return err + } + if _, err := client.CreatePermission(ctx, req); err != nil { return fmt.Errorf("failed to create custom permission: %w", err) } @@ -120,11 +124,14 @@ func createCustomRolesAndPermissions(ctx context.Context, client frontierv1beta1 str = "created custom roles :" var roleResp *connect.Response[frontierv1beta1.CreateRoleResponse] - var err error for _, role := range roles { - if roleResp, err = client.CreateRole(ctx, newRequest(&frontierv1beta1.CreateRoleRequest{ + roleReq, err := newRequest(&frontierv1beta1.CreateRoleRequest{ Body: role, - }, header)); err != nil { + }, header) + if err != nil { + return err + } + if roleResp, err = client.CreateRole(ctx, roleReq); err != nil { return fmt.Errorf("failed to create custom role: %w", err) } roleIDs = append(roleIDs, roleResp.Msg.GetRole().GetId()) @@ -180,9 +187,13 @@ func bootstrapData(ctx context.Context, client frontierv1beta1connect.FrontierSe reportPolicy = append(reportPolicy, []string{"CREATED_FOR", "ROLE", "RESOURCE"}) for idx, orgBody := range orgBodies { - userResp, err := client.CreateUser(ctx, newRequest(&frontierv1beta1.CreateUserRequest{ + userReq, err := newRequest(&frontierv1beta1.CreateUserRequest{ Body: userBodies[idx], - }, header)) + }, header) + if err != nil { + return err + } + userResp, err := client.CreateUser(ctx, userReq) if err != nil { return fmt.Errorf("failed to create sample user: %w", err) } @@ -193,9 +204,13 @@ func bootstrapData(ctx context.Context, client frontierv1beta1connect.FrontierSe userResp.Msg.GetUser().GetTitle(), }) - orgResp, err := client.CreateOrganization(ctx, newRequest(&frontierv1beta1.CreateOrganizationRequest{ + orgReq, err := newRequest(&frontierv1beta1.CreateOrganizationRequest{ Body: orgBody, - }, header)) + }, header) + if err != nil { + return err + } + orgResp, err := client.CreateOrganization(ctx, orgReq) if err != nil { return fmt.Errorf("failed to create sample organization: %w", err) } @@ -206,10 +221,14 @@ func bootstrapData(ctx context.Context, client frontierv1beta1connect.FrontierSe }) // create service user for an org - serviceUserResp, err := client.CreateServiceUser(ctx, newRequest(&frontierv1beta1.CreateServiceUserRequest{ + suReq, err := newRequest(&frontierv1beta1.CreateServiceUserRequest{ Body: &frontierv1beta1.ServiceUserRequestBody{Title: "sample service user"}, OrgId: orgResp.Msg.GetOrganization().GetId(), - }, header)) + }, header) + if err != nil { + return err + } + serviceUserResp, err := client.CreateServiceUser(ctx, suReq) if err != nil { return fmt.Errorf("failed to create sample service user: %w", err) } @@ -219,10 +238,14 @@ func bootstrapData(ctx context.Context, client frontierv1beta1connect.FrontierSe serviceUserResp.Msg.GetServiceuser().GetTitle(), }) // create service user credentials for an org - serviceUserCredentialResp, err := client.CreateServiceUserCredential(ctx, newRequest(&frontierv1beta1.CreateServiceUserCredentialRequest{ + suCredReq, err := newRequest(&frontierv1beta1.CreateServiceUserCredentialRequest{ Id: serviceUserResp.Msg.GetServiceuser().GetId(), Title: "service user id and pass", - }, header)) + }, header) + if err != nil { + return err + } + serviceUserCredentialResp, err := client.CreateServiceUserCredential(ctx, suCredReq) if err != nil { return fmt.Errorf("failed to generate sample service user password: %w", err) } @@ -234,9 +257,13 @@ func bootstrapData(ctx context.Context, client frontierv1beta1connect.FrontierSe // create project inside org projBodies[idx].OrgId = orgResp.Msg.GetOrganization().GetId() - projResp, err := client.CreateProject(ctx, newRequest(&frontierv1beta1.CreateProjectRequest{ + projReq, err := newRequest(&frontierv1beta1.CreateProjectRequest{ Body: projBodies[idx], - }, header)) + }, header) + if err != nil { + return err + } + projResp, err := client.CreateProject(ctx, projReq) if err != nil { return fmt.Errorf("failed to create sample project: %w", err) } @@ -249,10 +276,14 @@ func bootstrapData(ctx context.Context, client frontierv1beta1connect.FrontierSe // create resource inside project resourceBodies[idx].Principal = userResp.Msg.GetUser().GetId() - resrcResp, err := client.CreateProjectResource(ctx, newRequest(&frontierv1beta1.CreateProjectResourceRequest{ + resrcReq, err := newRequest(&frontierv1beta1.CreateProjectResourceRequest{ ProjectId: projResp.Msg.GetProject().GetId(), Body: resourceBodies[idx], - }, header)) + }, header) + if err != nil { + return err + } + resrcResp, err := client.CreateProjectResource(ctx, resrcReq) if err != nil { return fmt.Errorf("failed to create sample resource: %w", err) } @@ -266,14 +297,18 @@ func bootstrapData(ctx context.Context, client frontierv1beta1connect.FrontierSe // create sample policy resource := fmt.Sprintf("%s:%s", samplePolicyNamespace[idx], resrcResp.Msg.GetResource().GetId()) user := fmt.Sprintf("%s:%s", "app/user", userResp.Msg.GetUser().GetId()) - policyResp, err := client.CreatePolicy(ctx, newRequest(&frontierv1beta1.CreatePolicyRequest{ + policyReq, err := newRequest(&frontierv1beta1.CreatePolicyRequest{ Body: &frontierv1beta1.PolicyRequestBody{ RoleId: samplePolicyRole[idx], Resource: resource, Principal: user, Title: "Sample Policy", }, - }, header)) + }, header) + if err != nil { + return err + } + policyResp, err := client.CreatePolicy(ctx, policyReq) if err != nil { return fmt.Errorf("failed to create sample policy %w", err) } diff --git a/cmd/user.go b/cmd/user.go index 7ccf347e9..f8aa5492f 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -79,9 +79,13 @@ func createUserCommand(cliConfig *Config) *cli.Command { return err } - res, err := client.CreateUser(cmd.Context(), newRequest(&frontierv1beta1.CreateUserRequest{ + req, err := newRequest(&frontierv1beta1.CreateUserRequest{ Body: &reqBody, - }, header)) + }, header) + if err != nil { + return err + } + res, err := client.CreateUser(cmd.Context(), req) if err != nil { return err } From 97959553bc542fb1b5ab64e5a1f6d14e632073a1 Mon Sep 17 00:00:00 2001 From: Abhishek Sah Date: Wed, 18 Feb 2026 14:40:53 +0530 Subject: [PATCH 5/5] fix(cmd): set 30s HTTP timeout on CLI client to prevent hanging Replace http.DefaultClient (no timeout) with a client configured with a 30-second timeout to prevent CLI commands from hanging indefinitely if the server stalls. Co-Authored-By: Claude Opus 4.6 --- cmd/client.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/client.go b/cmd/client.go index 9bd503f47..d7e99fa07 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -4,23 +4,28 @@ import ( "fmt" "net/http" "strings" + "time" frontierv1beta1connect "github.com/raystack/frontier/proto/v1beta1/frontierv1beta1connect" "github.com/spf13/cobra" ) +var httpClient = &http.Client{ + Timeout: 30 * time.Second, +} + func createClient(host string) (frontierv1beta1connect.FrontierServiceClient, error) { if host == "" { return nil, ErrClientConfigHostNotFound } - return frontierv1beta1connect.NewFrontierServiceClient(http.DefaultClient, ensureHTTPScheme(host)), nil + return frontierv1beta1connect.NewFrontierServiceClient(httpClient, ensureHTTPScheme(host)), nil } func createAdminClient(host string) (frontierv1beta1connect.AdminServiceClient, error) { if host == "" { return nil, ErrClientConfigHostNotFound } - return frontierv1beta1connect.NewAdminServiceClient(http.DefaultClient, ensureHTTPScheme(host)), nil + return frontierv1beta1connect.NewAdminServiceClient(httpClient, ensureHTTPScheme(host)), nil } func ensureHTTPScheme(host string) string {