From 4ec247d1e6a65a714a7c80caf62e473ea8706d03 Mon Sep 17 00:00:00 2001 From: Brian Derr Date: Wed, 10 Apr 2024 22:32:14 -0700 Subject: [PATCH 1/2] all crud cmds working --- api/roles.go | 144 ++++++---------- api/roles_types.go | 311 +++++++++++++++++++++++++++++++++++ cmd/humioctl/roles.go | 37 +++++ cmd/humioctl/roles_create.go | 45 +++++ cmd/humioctl/roles_delete.go | 32 ++++ cmd/humioctl/roles_list.go | 46 ++++++ cmd/humioctl/roles_show.go | 24 +++ cmd/humioctl/roles_update.go | 55 +++++++ cmd/humioctl/root.go | 1 + go.mod | 4 +- go.sum | 8 + 11 files changed, 613 insertions(+), 94 deletions(-) create mode 100644 api/roles_types.go create mode 100644 cmd/humioctl/roles.go create mode 100644 cmd/humioctl/roles_create.go create mode 100644 cmd/humioctl/roles_delete.go create mode 100644 cmd/humioctl/roles_list.go create mode 100644 cmd/humioctl/roles_show.go create mode 100644 cmd/humioctl/roles_update.go diff --git a/api/roles.go b/api/roles.go index 6979aad..c45549d 100644 --- a/api/roles.go +++ b/api/roles.go @@ -2,6 +2,7 @@ package api import ( "fmt" + graphql "github.com/cli/shurcooL-graphql" ) @@ -9,118 +10,76 @@ type Roles struct { client *Client } +func (c *Client) Roles() *Roles { return &Roles{client: c} } + type Role struct { - ID string `graphql:"id"` - DisplayName string `graphql:"displayName"` - Color string `graphql:"color"` - Description string `graphql:"description"` - ViewPermissions []string `graphql:"viewPermissions"` - SystemPermissions []string `graphql:"systemPermissions"` - OrgPermissions []string `graphql:"organizationPermissions"` + ID string `json:"id"` + DisplayName string `json:"displayName"` + Color string `json:"color,omitempty"` + Description string `json:"description,omitempty"` + ViewPermissions []string `json:"viewPermissions"` + SystemPermissions []string `json:"systemPermissions,omitempty"` + OrgPermissions []string `graphql:"organizationPermissions" json:"organizationPermissions,omitempty"` + GroupsCount int `json:"groupsCount"` + UsersCount int `json:"usersCount"` } -func (c *Client) Roles() *Roles { return &Roles{client: c} } - +// List returns a list of roles in the Humio instance. func (r *Roles) List() ([]Role, error) { var query struct { - Roles struct { - Roles []Role - } `graphql:"roles()"` + Roles []Role `graphql:"roles"` } err := r.client.Query(&query, nil) - var RolesList []Role - if err == nil { - RolesList = query.Roles.Roles + if err != nil { + return nil, err } - return RolesList, nil + return query.Roles, nil } -func (r *Roles) Create(role *Role) error { +// Create adds a new role to the Humio instance. +func (r *Roles) Create(role AddRoleInput) error { var mutation struct { - Role `graphql:"createRole(input: {displayName: $displayName, viewPermissions: $permissions, color: $color, systemPermissions: $systemPermissions, organizationPermissions: $orgPermissions})"` + Results struct { + Role struct { + ID string + } + } `graphql:"createRole(input:$input)"` } - - viewPermissions := make([]graphql.String, len(role.ViewPermissions)) - for i, permission := range role.ViewPermissions { - viewPermissions[i] = graphql.String(permission) - } - - systemPermissions := make([]graphql.String, len(role.SystemPermissions)) - for i, permission := range role.SystemPermissions { - systemPermissions[i] = graphql.String(permission) - } - - orgPermissions := make([]graphql.String, len(role.OrgPermissions)) - for i, permission := range role.OrgPermissions { - orgPermissions[i] = graphql.String(permission) - } - variables := map[string]interface{}{ - "displayName": graphql.String(role.DisplayName), - "color": graphql.String(role.Color), - "description": graphql.String(role.Description), - "viewPermissions": viewPermissions, - "systemPermissions": systemPermissions, - "orgPermissions": orgPermissions, + "input": role, } - - return r.client.Mutate(mutation, variables) + return r.client.Mutate(&mutation, variables) } -func (r *Roles) Update(rolename string, newRole *Role) error { - roleId, err := r.GetRoleID(rolename) - if roleId == "" || err != nil { - return fmt.Errorf("unable to find role") - } - - if newRole == nil { - return fmt.Errorf("new role values must not be nil") - } - +// Upddate updates a role in the Humio instance. +func (r *Roles) Update(role UpdateRoleInput) error { var mutation struct { - Role `graphql:"updateRole(input: {roleId: $roleId, displayName: $displayName, color: $color, description: $description, viewPermissions: $viewPermissions, systemPermissions: $systemPermissions, organizationPermissions: $orgPermissions})"` - } - - viewPermissions := make([]graphql.String, len(newRole.ViewPermissions)) - for i, permission := range newRole.ViewPermissions { - viewPermissions[i] = graphql.String(permission) - } - - systemPermissions := make([]graphql.String, len(newRole.SystemPermissions)) - for i, permission := range newRole.SystemPermissions { - systemPermissions[i] = graphql.String(permission) - } - - orgPermissions := make([]graphql.String, len(newRole.OrgPermissions)) - for i, permission := range newRole.OrgPermissions { - orgPermissions[i] = graphql.String(permission) + Results struct { + Role struct { + ID string + } + } `graphql:"updateRole(input: $input)"` } variables := map[string]interface{}{ - "roleId": graphql.String(roleId), - "displayName": graphql.String(newRole.DisplayName), - "color": graphql.String(newRole.Color), - "description": graphql.String(newRole.Description), - "viewPermissions": viewPermissions, - "systemPermissions": systemPermissions, - "organizationPermissions": orgPermissions, + "input": role, } - return r.client.Mutate(mutation, variables) + return r.client.Mutate(&mutation, variables) } -func (r *Roles) RemoveRole(rolename string) error { +// Delete removes a role from the Humio instance. +func (r *Roles) Delete(name string) error { var mutation struct { - RemoveRole struct { - // We have to make a selection, so just take __typename - Typename graphql.String `graphql:"__typename"` - } `graphql:"removeRole(input: {roleId: $roleId})"` + Result struct { + Result bool + } `graphql:"removeRole(roleId: $roleId)"` } - role, err := r.client.Roles().Get(rolename) + role, err := r.client.Roles().Get(name) if err != nil { return err } @@ -128,44 +87,43 @@ func (r *Roles) RemoveRole(rolename string) error { variables := map[string]interface{}{ "roleId": graphql.String(role.ID), } - - return r.client.Mutate(mutation, variables) + return r.client.Mutate(&mutation, variables) } -func (r *Roles) Get(rolename string) (*Role, error) { - roleId, err := r.GetRoleID(rolename) +// Get returns a role given its name. +func (r *Roles) Get(name string) (*Role, error) { + roleId, err := r.GetRoleID(name) if roleId == "" || err != nil { return nil, fmt.Errorf("unable to get role id") } var query struct { - Role `graphql:"role(roleId: $roleId)"` + Role Role `graphql:"role(roleId: $roleId)"` } variables := map[string]interface{}{ "roleId": graphql.String(roleId), } - err = r.client.Query(query, variables) + err = r.client.Query(&query, variables) if err != nil { return nil, err } - return &query.Role, nil } -func (r *Roles) GetRoleID(rolename string) (string, error) { +// GetRoleID returns the ID of a role given its name. +func (r *Roles) GetRoleID(name string) (string, error) { roles, err := r.List() if err != nil { return "", fmt.Errorf("unable to list roles: %w", err) } var roleId string for _, role := range roles { - if role.DisplayName == rolename { - roleId = role.ID + if role.DisplayName == name { + roleId = string(role.ID) break } } - return roleId, nil } diff --git a/api/roles_types.go b/api/roles_types.go new file mode 100644 index 0000000..36ed20f --- /dev/null +++ b/api/roles_types.go @@ -0,0 +1,311 @@ +package api + +import graphql "github.com/cli/shurcooL-graphql" + +// ViewPermission is a type that represents the view permissions that can be assigned to a role. +type ViewPermission string + +const ( + ViewPermissionChangeUserAccess ViewPermission = "ChangeUserAccess" + ViewPermissionChangeTriggersAndActions ViewPermission = "ChangeTriggersAndActions" + ViewPermissionChangeDashboards ViewPermission = "ChangeDashboards" + ViewPermissionChangeDashboardReadonlyToken ViewPermission = "ChangeDashboardReadonlyToken" + ViewPermissionChangeFiles ViewPermission = "ChangeFiles" + ViewPermissionChangeInteractions ViewPermission = "ChangeInteractions" + ViewPermissionChangeParsers ViewPermission = "ChangeParsers" + ViewPermissionChangeSavedQueries ViewPermission = "ChangeSavedQueries" + ViewPermissionConnectView ViewPermission = "ConnectView" + ViewPermissionChangeDataDeletionPermissions ViewPermission = "ChangeDataDeletionPermissions" + ViewPermissionChangeRetention ViewPermission = "ChangeRetention" + ViewPermissionChangeDefaultSearchSettings ViewPermission = "ChangeDefaultSearchSettings" + ViewPermissionChangeS3ArchivingSettings ViewPermission = "ChangeS3ArchivingSettings" + ViewPermissionDeleteDataSources ViewPermission = "DeleteDataSources" + ViewPermissionDeleteRepositoryOrView ViewPermission = "DeleteRepositoryOrView" + ViewPermissionDeleteEvents ViewPermission = "DeleteEvents" + ViewPermissionReadAccess ViewPermission = "ReadAccess" + ViewPermissionChangeIngestTokens ViewPermission = "ChangeIngestTokens" + ViewPermissionChangePackages ViewPermission = "ChangePackages" + ViewPermissionChangeViewOrRepositoryDescription ViewPermission = "ChangeViewOrRepositoryDescription" + ViewPermissionChangeConnections ViewPermission = "ChangeConnections" + ViewPermissionEventForwarding ViewPermission = "EventForwarding" + ViewPermissionQueryDashboard ViewPermission = "QueryDashboard" + ViewPermissionChangeViewOrRepositoryPermissions ViewPermission = "ChangeViewOrRepositoryPermissions" + ViewPermissionChangeFdrFeeds ViewPermission = "ChangeFdrFeeds" + ViewPermissionOrganizationOwnedQueries ViewPermission = "OrganizationOwnedQueries" + ViewPermissionReadExternalFunctions ViewPermission = "ReadExternalFunctions" + ViewPermissionChangeIngestFeeds ViewPermission = "ChangeIngestFeeds" + ViewPermissionChangeScheduledReports ViewPermission = "ChangeScheduledReports" +) + +// Get returns the ViewPermission value for the given string, if it exists. +func (vp ViewPermission) Get(p string) (ViewPermission, bool) { + switch ViewPermission(p) { + case ViewPermissionChangeUserAccess, + ViewPermissionChangeTriggersAndActions, + ViewPermissionChangeDashboards, + ViewPermissionChangeDashboardReadonlyToken, + ViewPermissionChangeFiles, + ViewPermissionChangeInteractions, + ViewPermissionChangeParsers, + ViewPermissionChangeSavedQueries, + ViewPermissionConnectView, + ViewPermissionChangeDataDeletionPermissions, + ViewPermissionChangeRetention, + ViewPermissionChangeDefaultSearchSettings, + ViewPermissionChangeS3ArchivingSettings, + ViewPermissionDeleteDataSources, + ViewPermissionDeleteRepositoryOrView, + ViewPermissionDeleteEvents, + ViewPermissionReadAccess, + ViewPermissionChangeIngestTokens, + ViewPermissionChangePackages, + ViewPermissionChangeViewOrRepositoryDescription, + ViewPermissionChangeConnections, + ViewPermissionEventForwarding, + ViewPermissionQueryDashboard, + ViewPermissionChangeViewOrRepositoryPermissions, + ViewPermissionChangeFdrFeeds, + ViewPermissionOrganizationOwnedQueries, + ViewPermissionReadExternalFunctions, + ViewPermissionChangeIngestFeeds, + ViewPermissionChangeScheduledReports: + return ViewPermission(p), true + default: + return "", false + } +} + +// SystemPermission is a type that represents the system permissions that can be assigned to a role. +type SystemPermission string + +const ( + SystemPermissionReadHealthCheck SystemPermission = "ReadHealthCheck" + SystemPermissionManageOrganizations SystemPermission = "ManageOrganizations" + SystemPermissionImportOrganization SystemPermission = "ImportOrganization" + SystemPermissionDeleteOrganizations SystemPermission = "DeleteOrganizations" + SystemPermissionChangeSystemPermissions SystemPermission = "ChangeSystemPermissions" + SystemPermissionManageCluster SystemPermission = "ManageCluster" + SystemPermissionIngestAcrossAllReposWithinCluster SystemPermission = "IngestAcrossAllReposWithinCluster" + SystemPermissionDeleteHumioOwnedRepositoryOrView SystemPermission = "DeleteHumioOwnedRepositoryOrView" + SystemPermissionChangeUsername SystemPermission = "ChangeUsername" + SystemPermissionChangeFeatureFlags SystemPermission = "ChangeFeatureFlags" + SystemPermissionChangeSubdomains SystemPermission = "ChangeSubdomains" + SystemPermissionListSubdomains SystemPermission = "ListSubdomains" + SystemPermissionPatchGlobal SystemPermission = "PatchGlobal" + SystemPermissionChangeBucketStorage SystemPermission = "ChangeBucketStorage" + SystemPermissionManageOrganizationLinks SystemPermission = "ManageOrganizationLinks" +) + +// Get returns the SystemPermission value for the given string, if it exists. +func (sp SystemPermission) Get(p string) (SystemPermission, bool) { + switch SystemPermission(p) { + case SystemPermissionReadHealthCheck, + SystemPermissionManageOrganizations, + SystemPermissionImportOrganization, + SystemPermissionDeleteOrganizations, + SystemPermissionChangeSystemPermissions, + SystemPermissionManageCluster, + SystemPermissionIngestAcrossAllReposWithinCluster, + SystemPermissionDeleteHumioOwnedRepositoryOrView, + SystemPermissionChangeUsername, + SystemPermissionChangeFeatureFlags, + SystemPermissionChangeSubdomains, + SystemPermissionListSubdomains, + SystemPermissionPatchGlobal, + SystemPermissionChangeBucketStorage, + SystemPermissionManageOrganizationLinks: + return SystemPermission(p), true + default: + return "", false + } +} + +// OrganizationPermission is a type that represents the organization permissions that can be assigned to a role. +type OrganizationPermission string + +const ( + OrganizationPermissionExportOrganization OrganizationPermission = "ExportOrganization" + OrganizationPermissionChangeOrganizationPermissions OrganizationPermission = "ChangeOrganizationPermissions" + OrganizationPermissionChangeIdentityProviders OrganizationPermission = "ChangeIdentityProviders" + OrganizationPermissionCreateRepository OrganizationPermission = "CreateRepository" + OrganizationPermissionManageUsers OrganizationPermission = "ManageUsers" + OrganizationPermissionViewUsage OrganizationPermission = "ViewUsage" + OrganizationPermissionChangeOrganizationSettings OrganizationPermission = "ChangeOrganizationSettings" + OrganizationPermissionChangeIPFilters OrganizationPermission = "ChangeIPFilters" + OrganizationPermissionChangeSessions OrganizationPermission = "ChangeSessions" + OrganizationPermissionChangeAllViewOrRepositoryPermissions OrganizationPermission = "ChangeAllViewOrRepositoryPermissions" + OrganizationPermissionIngestAcrossAllReposWithinOrganization OrganizationPermission = "IngestAcrossAllReposWithinOrganization" + OrganizationPermissionDeleteAllRepositories OrganizationPermission = "DeleteAllRepositories" + OrganizationPermissionDeleteAllViews OrganizationPermission = "DeleteAllViews" + OrganizationPermissionViewAllInternalNotifications OrganizationPermission = "ViewAllInternalNotifications" + OrganizationPermissionChangeFleetManagement OrganizationPermission = "ChangeFleetManagement" + OrganizationPermissionViewFleetManagement OrganizationPermission = "ViewFleetManagement" + OrganizationPermissionChangeTriggersToRunAsOtherUsers OrganizationPermission = "ChangeTriggersToRunAsOtherUsers" + OrganizationPermissionMonitorQueries OrganizationPermission = "MonitorQueries" + OrganizationPermissionBlockQueries OrganizationPermission = "BlockQueries" + OrganizationPermissionChangeSecurityPolicies OrganizationPermission = "ChangeSecurityPolicies" + OrganizationPermissionChangeExternalFunctions OrganizationPermission = "ChangeExternalFunctions" +) + +// Get returns the OrganizationPermission value for the given string, if it exists. +func (op OrganizationPermission) Get(p string) (OrganizationPermission, bool) { + switch OrganizationPermission(p) { + case OrganizationPermissionExportOrganization, + OrganizationPermissionChangeOrganizationPermissions, + OrganizationPermissionChangeIdentityProviders, + OrganizationPermissionCreateRepository, + OrganizationPermissionManageUsers, + OrganizationPermissionViewUsage, + OrganizationPermissionChangeOrganizationSettings, + OrganizationPermissionChangeIPFilters, + OrganizationPermissionChangeSessions, + OrganizationPermissionChangeAllViewOrRepositoryPermissions, + OrganizationPermissionIngestAcrossAllReposWithinOrganization, + OrganizationPermissionDeleteAllRepositories, + OrganizationPermissionDeleteAllViews, + OrganizationPermissionViewAllInternalNotifications, + OrganizationPermissionChangeFleetManagement, + OrganizationPermissionViewFleetManagement, + OrganizationPermissionChangeTriggersToRunAsOtherUsers, + OrganizationPermissionMonitorQueries, + OrganizationPermissionBlockQueries, + OrganizationPermissionChangeSecurityPolicies, + OrganizationPermissionChangeExternalFunctions: + return OrganizationPermission(p), true + default: + return "", false + } +} + +type ObjectAction string + +const ( + ObjectActionUnknown ObjectAction = "Unknown" + ObjectActionReadOnlyAndHidden ObjectAction = "ReadOnlyAndHidden" + ObjectActionReadWRiteAndVisible ObjectAction = "ReadWRiteAndVisible" +) + +func (op OrganizationPermission) Get(p string) (OrganizationPermission, bool) { + switch OrganizationPermission(p) { + case OrganizationPermissionExportOrganization, + OrganizationPermissionChangeOrganizationPermissions, + OrganizationPermissionChangeIdentityProviders, + OrganizationPermissionCreateRepository, + OrganizationPermissionManageUsers, + OrganizationPermissionViewUsage, + OrganizationPermissionChangeOrganizationSettings, + OrganizationPermissionChangeIPFilters, + OrganizationPermissionChangeSessions, + OrganizationPermissionChangeAllViewOrRepositoryPermissions, + OrganizationPermissionIngestAcrossAllReposWithinOrganization, + OrganizationPermissionDeleteAllRepositories, + OrganizationPermissionDeleteAllViews, + OrganizationPermissionViewAllInternalNotifications, + OrganizationPermissionChangeFleetManagement, + OrganizationPermissionViewFleetManagement, + OrganizationPermissionChangeTriggersToRunAsOtherUsers, + OrganizationPermissionMonitorQueries, + OrganizationPermissionBlockQueries, + OrganizationPermissionChangeSecurityPolicies, + OrganizationPermissionChangeExternalFunctions: + return OrganizationPermission(p), true + default: + return "", false + } +} + +// AddRoleInput is the struct of input variables passed to the `createRole()` API mutation. +type AddRoleInput struct { + DisplayName graphql.String `json:"displayName"` + Color *graphql.String `json:"color,omitempty"` // The color of the role in RGB hexadecimal, e.g. #FF0000. + ViewPermissions []ViewPermission `json:"viewPermissions"` + SystemPermissions *[]SystemPermission `json:"systemPermissions,omitempty"` + OrgPermissions *[]OrganizationPermission `json:"organizationPermissions,omitempty"` + ObjectAction *ObjectAction `json:"objectAction,omitempty"` // Undocumented field. + + // Oddly, the GraphQL API does not expose this field. + // Description *graphql.String `json:"description"` +} + +// NewAddRoleInput returns the AddRoleInput struct initialized with the given values. +func NewAddRoleInput(name string, color *string, viewPermissions, systemPermissions, orgPermissions []string) AddRoleInput { + ari := AddRoleInput{ + DisplayName: graphql.String(name), + } + + if color != nil { + ari.Color = graphql.NewString(graphql.String(*color)) + } + + for _, permission := range viewPermissions { + if vp, ok := ViewPermission(permission).Get(permission); ok { + ari.ViewPermissions = append(ari.ViewPermissions, vp) + } + } + + sysPerms := make([]SystemPermission, 0, len(systemPermissions)) + for _, permission := range systemPermissions { + if sp, ok := SystemPermission(permission).Get(permission); ok { + sysPerms = append(sysPerms, sp) + } + } + ari.SystemPermissions = &sysPerms + + orgPerms := make([]OrganizationPermission, 0, len(orgPermissions)) + for _, permission := range orgPermissions { + if op, ok := OrganizationPermission(permission).Get(permission); ok { + orgPerms = append(orgPerms, op) + } + } + ari.OrgPermissions = &orgPerms + return ari +} + +// UpdateRoleInput is the struct of input variables passed to the `updateRole()` API mutation. +type UpdateRoleInput struct { + ID graphql.String `json:"roleId"` + DisplayName graphql.String `json:"displayName"` + ViewPermissions []ViewPermission `json:"viewPermissions"` + Description *graphql.String `json:"description,omitempty"` + Color *graphql.String `json:"color,omitempty"` + SystemPermissions *[]SystemPermission `json:"systemPermissions,omitempty"` + OrganizationPermissions *[]OrganizationPermission `json:"organizationPermissions,omitempty"` + ObjectAction *ObjectAction `json:"objectAction,omitempty"` +} + +// NewUpdateRoleInput returns the UpdateRoleInput struct initialized with the given values. +func NewUpdateRoleInput(id, name string, viewPermissions, systemPermissions, orgPermissions []string, color *string) UpdateRoleInput { + uri := UpdateRoleInput{ + ID: graphql.String(id), + DisplayName: graphql.String(name), + } + + if color != nil { + uri.Color = graphql.NewString(graphql.String(*color)) + } + + viewPerms := make([]ViewPermission, 0, len(viewPermissions)) + for _, permission := range viewPermissions { + if vp, ok := ViewPermission(permission).Get(permission); ok { + viewPerms = append(viewPerms, vp) + } + } + uri.ViewPermissions = viewPerms + + sysPerms := make([]SystemPermission, 0, len(systemPermissions)) + for _, permission := range systemPermissions { + if sp, ok := SystemPermission(permission).Get(permission); ok { + sysPerms = append(sysPerms, sp) + } + } + uri.SystemPermissions = &sysPerms + + orgPerms := make([]OrganizationPermission, 0, len(orgPermissions)) + for _, permission := range orgPermissions { + if op, ok := OrganizationPermission(permission).Get(permission); ok { + orgPerms = append(orgPerms, op) + } + } + uri.OrganizationPermissions = &orgPerms + return uri +} diff --git a/cmd/humioctl/roles.go b/cmd/humioctl/roles.go new file mode 100644 index 0000000..25974bb --- /dev/null +++ b/cmd/humioctl/roles.go @@ -0,0 +1,37 @@ +package main + +import ( + "strings" + + "github.com/humio/cli/api" + "github.com/humio/cli/cmd/internal/format" + "github.com/spf13/cobra" +) + +func newRolesCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "roles", + Short: "Manage roles", + } + + cmd.AddCommand(newRolesShowCmd()) + cmd.AddCommand(newRolesListCmd()) + cmd.AddCommand(newRolesCreateCmd()) + cmd.AddCommand(newRolesUpdateCmd()) + cmd.AddCommand(newRolesDeleteCmd()) + + return cmd +} + +func printRoleDetailsTable(cmd *cobra.Command, role api.Role) { + details := [][]format.Value{ + {format.String("ID"), format.String(role.ID)}, + {format.String("Name"), format.String(role.DisplayName)}, + {format.String("Description"), format.String(role.Description)}, + {format.String("View Permissions"), format.String(strings.Join(role.ViewPermissions, "\n"))}, + {format.String("System Permissions"), format.String(strings.Join(role.SystemPermissions, "\n"))}, + {format.String("Organization Permissions"), format.String(strings.Join(role.OrgPermissions, "\n"))}, + } + + printDetailsTable(cmd, details) +} diff --git a/cmd/humioctl/roles_create.go b/cmd/humioctl/roles_create.go new file mode 100644 index 0000000..786498f --- /dev/null +++ b/cmd/humioctl/roles_create.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + + "github.com/humio/cli/api" + "github.com/spf13/cobra" +) + +func newRolesCreateCmd() *cobra.Command { + var colorFlag stringPtrFlag + var orgPermissionsFlag, sysPermissionsFlag, viewPermissionsFlag []string + + cmd := cobra.Command{ + Use: "create ", + Short: "Create a role.", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + client := NewApiClient(cmd) + + role := api.NewAddRoleInput( + args[0], + colorFlag.value, + viewPermissionsFlag, + sysPermissionsFlag, + orgPermissionsFlag, + ) + + err := client.Roles().Create(role) + exitOnError(cmd, err, "Error creating role") + + fmt.Fprintf(cmd.OutOrStdout(), "Successfully created role %s\n", args[0]) + + }, + } + + cmd.Flags().Var(&colorFlag, "color", "The color of the role in RGB hexadecimal, e.g. #FF0000.") + cmd.Flags().StringSliceVar(&orgPermissionsFlag, "org-permissions", []string{}, "The organization permissions of the role.") + cmd.Flags().StringSliceVar(&sysPermissionsFlag, "system-permissions", []string{}, "The system permissions of the role.") + cmd.Flags().StringSliceVar(&viewPermissionsFlag, "view-permissions", []string{}, "(required) The view permissions of the role.") + + cmd.MarkFlagRequired("view-permissions") + + return &cmd +} diff --git a/cmd/humioctl/roles_delete.go b/cmd/humioctl/roles_delete.go new file mode 100644 index 0000000..dae46c5 --- /dev/null +++ b/cmd/humioctl/roles_delete.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func newRolesDeleteCmd() *cobra.Command { + cmd := cobra.Command{ + Use: "delete [flags] ", + Short: "Delete a role.", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + repo := args[0] + client := NewApiClient(cmd) + + roleId, err := client.Roles().GetRoleID(repo) + exitOnError(cmd, err, "Error getting role ID") + if roleId == "" { + exitOnError(cmd, fmt.Errorf("role %q not found", repo), "Role not found") + } + + err = client.Roles().Delete(repo) + exitOnError(cmd, err, "Error removing repository") + + fmt.Fprintf(cmd.OutOrStdout(), "Successfully deleted repository: %q\n", repo) + }, + } + + return &cmd +} diff --git a/cmd/humioctl/roles_list.go b/cmd/humioctl/roles_list.go new file mode 100644 index 0000000..76b9615 --- /dev/null +++ b/cmd/humioctl/roles_list.go @@ -0,0 +1,46 @@ +package main + +import ( + "sort" + + "github.com/humio/cli/api" + "github.com/humio/cli/cmd/internal/format" + "github.com/spf13/cobra" +) + +func newRolesListCmd() *cobra.Command { + cmd := cobra.Command{ + Use: "list [flags]", + Short: "List roles", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + client := NewApiClient(cmd) + + roles, err := client.Roles().List() + exitOnError(cmd, err, "Error fetching roles") + + sort.Slice(roles, func(i, j int) bool { + var a, b api.Role + a = roles[i] + b = roles[j] + + return a.DisplayName < b.DisplayName + }) + + rows := make([][]format.Value, len(roles)) + for i, view := range roles { + rows[i] = []format.Value{ + format.String(view.DisplayName), + format.String(view.Description), + format.Int(view.GroupsCount), + format.Int(view.UsersCount), + format.String(view.ID), + } + } + + printOverviewTable(cmd, []string{"Name", "Description", "Groups", "Users", "ID"}, rows) + }, + } + + return &cmd +} diff --git a/cmd/humioctl/roles_show.go b/cmd/humioctl/roles_show.go new file mode 100644 index 0000000..89b7c5c --- /dev/null +++ b/cmd/humioctl/roles_show.go @@ -0,0 +1,24 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +func newRolesShowCmd() *cobra.Command { + cmd := cobra.Command{ + Use: "show ", + Short: "Show details about a role.", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + roleName := args[0] + client := NewApiClient(cmd) + + role, err := client.Roles().Get(roleName) + exitOnError(cmd, err, "Error fetching role") + + printRoleDetailsTable(cmd, *role) + }, + } + + return &cmd +} diff --git a/cmd/humioctl/roles_update.go b/cmd/humioctl/roles_update.go new file mode 100644 index 0000000..9ad2436 --- /dev/null +++ b/cmd/humioctl/roles_update.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/humio/cli/api" +) + +func newRolesUpdateCmd() *cobra.Command { + var colorFlag stringPtrFlag + var orgPermissionsFlag, sysPermissionsFlag, viewPermissionsFlag []string + + cmd := cobra.Command{ + Use: "update [flags] ", + Short: "Updates a role.", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + roleName := args[0] + client := NewApiClient(cmd) + + if viewPermissionsFlag == nil && sysPermissionsFlag == nil && orgPermissionsFlag == nil && colorFlag.value == nil { + exitOnError(cmd, fmt.Errorf("you must specify at least one flag to update"), "Nothing specified to update") + } + + roleId, err := client.Roles().GetRoleID(roleName) + exitOnError(cmd, err, "Error getting role ID") + if roleId == "" { + exitOnError(cmd, fmt.Errorf("role %q not found", roleName), "Role not found") + } + + roleUpdate := api.NewUpdateRoleInput( + roleId, + roleName, + viewPermissionsFlag, + sysPermissionsFlag, + orgPermissionsFlag, + colorFlag.value, + ) + + err = client.Roles().Update(roleUpdate) + exitOnError(cmd, err, "Error updating role") + + fmt.Fprintf(cmd.OutOrStdout(), "Successfully updated role %q\n", roleName) + }, + } + + cmd.Flags().Var(&colorFlag, "color", "The color of the role in RGB hexadecimal, e.g. #FF0000.") + cmd.Flags().StringSliceVar(&orgPermissionsFlag, "org-permissions", []string{}, "The organization permissions of the role.") + cmd.Flags().StringSliceVar(&sysPermissionsFlag, "system-permissions", []string{}, "The system permissions of the role.") + cmd.Flags().StringSliceVar(&viewPermissionsFlag, "view-permissions", []string{}, "(required) The view permissions of the role.") + + return &cmd +} diff --git a/cmd/humioctl/root.go b/cmd/humioctl/root.go index 5598f98..7d9219a 100644 --- a/cmd/humioctl/root.go +++ b/cmd/humioctl/root.go @@ -115,6 +115,7 @@ Common Management Commands: rootCmd.AddCommand(newCompletionCmd()) rootCmd.AddCommand(newLicenseCmd()) rootCmd.AddCommand(newReposCmd()) + rootCmd.AddCommand(newRolesCmd()) rootCmd.AddCommand(newSearchCmd()) rootCmd.AddCommand(newStatusCmd()) rootCmd.AddCommand(newHealthCmd()) diff --git a/go.mod b/go.mod index a2aae09..ae3b7e8 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/humio/cli -go 1.20 +go 1.21 + +toolchain go1.22.1 require ( github.com/cli/shurcooL-graphql v0.0.4 diff --git a/go.sum b/go.sum index a0c85db..2a19c4f 100644 --- a/go.sum +++ b/go.sum @@ -4,12 +4,15 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -17,7 +20,9 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k= @@ -32,7 +37,9 @@ github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOS github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= @@ -75,6 +82,7 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= From 551339b10bac3846b133295317e445f15f9cddb8 Mon Sep 17 00:00:00 2001 From: Brian Derr Date: Thu, 11 Apr 2024 11:20:04 -0700 Subject: [PATCH 2/2] add append flag --- api/roles.go | 25 +++++++++++++-- api/roles_types.go | 62 ++++++++++++------------------------ cmd/humioctl/roles_create.go | 2 -- cmd/humioctl/roles_update.go | 4 ++- 4 files changed, 47 insertions(+), 46 deletions(-) diff --git a/api/roles.go b/api/roles.go index c45549d..410a533 100644 --- a/api/roles.go +++ b/api/roles.go @@ -55,7 +55,28 @@ func (r *Roles) Create(role AddRoleInput) error { } // Upddate updates a role in the Humio instance. -func (r *Roles) Update(role UpdateRoleInput) error { +func (r *Roles) Update(update UpdateRoleInput, appendOp bool) error { + if appendOp { + role, err := r.Get(string(update.DisplayName)) + if err != nil { + return err + } + for _, viewPerm := range role.ViewPermissions { + if vp, ok := ViewPermission(viewPerm).Get(viewPerm); ok { + update.ViewPermissions = append(update.ViewPermissions, vp) + } + } + for _, sysPerm := range role.SystemPermissions { + if sp, ok := SystemPermission(sysPerm).Get(sysPerm); ok { + *update.SystemPermissions = append(*update.SystemPermissions, sp) + } + } + for _, orgPerm := range role.OrgPermissions { + if op, ok := OrganizationPermission(orgPerm).Get(orgPerm); ok { + *update.OrganizationPermissions = append(*update.OrganizationPermissions, op) + } + } + } var mutation struct { Results struct { Role struct { @@ -65,7 +86,7 @@ func (r *Roles) Update(role UpdateRoleInput) error { } variables := map[string]interface{}{ - "input": role, + "input": update, } return r.client.Mutate(&mutation, variables) diff --git a/api/roles_types.go b/api/roles_types.go index 36ed20f..016cd46 100644 --- a/api/roles_types.go +++ b/api/roles_types.go @@ -185,35 +185,6 @@ const ( ObjectActionReadWRiteAndVisible ObjectAction = "ReadWRiteAndVisible" ) -func (op OrganizationPermission) Get(p string) (OrganizationPermission, bool) { - switch OrganizationPermission(p) { - case OrganizationPermissionExportOrganization, - OrganizationPermissionChangeOrganizationPermissions, - OrganizationPermissionChangeIdentityProviders, - OrganizationPermissionCreateRepository, - OrganizationPermissionManageUsers, - OrganizationPermissionViewUsage, - OrganizationPermissionChangeOrganizationSettings, - OrganizationPermissionChangeIPFilters, - OrganizationPermissionChangeSessions, - OrganizationPermissionChangeAllViewOrRepositoryPermissions, - OrganizationPermissionIngestAcrossAllReposWithinOrganization, - OrganizationPermissionDeleteAllRepositories, - OrganizationPermissionDeleteAllViews, - OrganizationPermissionViewAllInternalNotifications, - OrganizationPermissionChangeFleetManagement, - OrganizationPermissionViewFleetManagement, - OrganizationPermissionChangeTriggersToRunAsOtherUsers, - OrganizationPermissionMonitorQueries, - OrganizationPermissionBlockQueries, - OrganizationPermissionChangeSecurityPolicies, - OrganizationPermissionChangeExternalFunctions: - return OrganizationPermission(p), true - default: - return "", false - } -} - // AddRoleInput is the struct of input variables passed to the `createRole()` API mutation. type AddRoleInput struct { DisplayName graphql.String `json:"displayName"` @@ -223,14 +194,17 @@ type AddRoleInput struct { OrgPermissions *[]OrganizationPermission `json:"organizationPermissions,omitempty"` ObjectAction *ObjectAction `json:"objectAction,omitempty"` // Undocumented field. - // Oddly, the GraphQL API does not expose this field. - // Description *graphql.String `json:"description"` + // Oddly, the GraphQL API does not expose this field for the createRole mutation. + // Description *graphql.String `json:"description,omitempty"` } // NewAddRoleInput returns the AddRoleInput struct initialized with the given values. func NewAddRoleInput(name string, color *string, viewPermissions, systemPermissions, orgPermissions []string) AddRoleInput { ari := AddRoleInput{ DisplayName: graphql.String(name), + + // ViewPermissions is a required field, so we initialize it with an empty slice. + ViewPermissions: make([]ViewPermission, 0, len(viewPermissions)), } if color != nil { @@ -243,21 +217,27 @@ func NewAddRoleInput(name string, color *string, viewPermissions, systemPermissi } } - sysPerms := make([]SystemPermission, 0, len(systemPermissions)) - for _, permission := range systemPermissions { - if sp, ok := SystemPermission(permission).Get(permission); ok { - sysPerms = append(sysPerms, sp) + if len(systemPermissions) > 0 { + if ari.SystemPermissions == nil { + ari.SystemPermissions = &[]SystemPermission{} + } + for _, permission := range systemPermissions { + if sp, ok := SystemPermission(permission).Get(permission); ok { + *ari.SystemPermissions = append(*ari.SystemPermissions, sp) + } } } - ari.SystemPermissions = &sysPerms - orgPerms := make([]OrganizationPermission, 0, len(orgPermissions)) - for _, permission := range orgPermissions { - if op, ok := OrganizationPermission(permission).Get(permission); ok { - orgPerms = append(orgPerms, op) + if len(orgPermissions) > 0 { + if ari.OrgPermissions == nil { + ari.OrgPermissions = &[]OrganizationPermission{} + } + for _, permission := range orgPermissions { + if op, ok := OrganizationPermission(permission).Get(permission); ok { + *ari.OrgPermissions = append(*ari.OrgPermissions, op) + } } } - ari.OrgPermissions = &orgPerms return ari } diff --git a/cmd/humioctl/roles_create.go b/cmd/humioctl/roles_create.go index 786498f..d238863 100644 --- a/cmd/humioctl/roles_create.go +++ b/cmd/humioctl/roles_create.go @@ -39,7 +39,5 @@ func newRolesCreateCmd() *cobra.Command { cmd.Flags().StringSliceVar(&sysPermissionsFlag, "system-permissions", []string{}, "The system permissions of the role.") cmd.Flags().StringSliceVar(&viewPermissionsFlag, "view-permissions", []string{}, "(required) The view permissions of the role.") - cmd.MarkFlagRequired("view-permissions") - return &cmd } diff --git a/cmd/humioctl/roles_update.go b/cmd/humioctl/roles_update.go index 9ad2436..ab69a7d 100644 --- a/cmd/humioctl/roles_update.go +++ b/cmd/humioctl/roles_update.go @@ -9,6 +9,7 @@ import ( ) func newRolesUpdateCmd() *cobra.Command { + var appendFlag bool var colorFlag stringPtrFlag var orgPermissionsFlag, sysPermissionsFlag, viewPermissionsFlag []string @@ -39,13 +40,14 @@ func newRolesUpdateCmd() *cobra.Command { colorFlag.value, ) - err = client.Roles().Update(roleUpdate) + err = client.Roles().Update(roleUpdate, appendFlag) exitOnError(cmd, err, "Error updating role") fmt.Fprintf(cmd.OutOrStdout(), "Successfully updated role %q\n", roleName) }, } + cmd.Flags().BoolVar(&appendFlag, "append", false, "Append the specified permissions to the existing permissions.") cmd.Flags().Var(&colorFlag, "color", "The color of the role in RGB hexadecimal, e.g. #FF0000.") cmd.Flags().StringSliceVar(&orgPermissionsFlag, "org-permissions", []string{}, "The organization permissions of the role.") cmd.Flags().StringSliceVar(&sysPermissionsFlag, "system-permissions", []string{}, "The system permissions of the role.")