[MM-56000] Add LDAP job command to mmctl (#25633)

This commit is contained in:
Ben Schumacher 2024-04-22 12:19:53 +02:00 committed by GitHub
parent 8348acac49
commit 3dae305dc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 476 additions and 7 deletions

View File

@ -213,7 +213,7 @@ func noCompletion(_ *cobra.Command, _ []string, _ string) ([]string, cobra.Shell
type validateArgsFn func(ctx context.Context, c client.Client, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective)
func validateArgsWithClient(fn validateArgsFn) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { //nolint:unused // Remove with https://github.com/mattermost/mattermost/pull/25633
func validateArgsWithClient(fn validateArgsFn) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
ctx, cancel := context.WithTimeout(context.Background(), shellCompleteTimeout)
defer cancel()

View File

@ -5,10 +5,12 @@ package commands
import (
"context"
"fmt"
"net/http"
"github.com/spf13/cobra"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/client"
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
)
@ -40,11 +42,47 @@ var LdapIDMigrate = &cobra.Command{
RunE: withClient(ldapIDMigrateCmdF),
}
var LdapJobCmd = &cobra.Command{
Use: "job",
Short: "List and show LDAP sync jobs",
}
var LdapJobListCmd = &cobra.Command{
Use: "list",
Example: " ldap job list",
Short: "List LDAP sync jobs",
// Alisases cause error in zsh. Supposedly, completion V2 will fix that: https://github.com/spf13/cobra/pull/1146
// https://mattermost.atlassian.net/browse/MM-57062
// Aliases: []string{"ls"},
Args: cobra.NoArgs,
ValidArgsFunction: noCompletion,
RunE: withClient(ldapJobListCmdF),
}
var LdapJobShowCmd = &cobra.Command{
Use: "show [ldapJobID]",
Example: " import ldap show f3d68qkkm7n8xgsfxwuo498rah",
Short: "Show LDAP sync job",
ValidArgsFunction: validateArgsWithClient(ldapJobShowCompletionF),
RunE: withClient(ldapJobShowCmdF),
}
func init() {
LdapSyncCmd.Flags().Bool("include-removed-members", false, "Include members who left or were removed from a group-synced team/channel")
LdapJobListCmd.Flags().Int("page", 0, "Page number to fetch for the list of import jobs")
LdapJobListCmd.Flags().Int("per-page", 200, "Number of import jobs to be fetched")
LdapJobListCmd.Flags().Bool("all", false, "Fetch all import jobs. --page flag will be ignore if provided")
LdapJobCmd.AddCommand(
LdapJobListCmd,
LdapJobShowCmd,
)
LdapCmd.AddCommand(
LdapSyncCmd,
LdapIDMigrate,
LdapJobCmd,
)
RootCmd.AddCommand(LdapCmd)
}
@ -81,3 +119,27 @@ func ldapIDMigrateCmdF(c client.Client, cmd *cobra.Command, args []string) error
return nil
}
func ldapJobListCmdF(c client.Client, command *cobra.Command, args []string) error {
return jobListCmdF(c, command, model.JobTypeLdapSync)
}
func ldapJobShowCmdF(c client.Client, command *cobra.Command, args []string) error {
job, _, err := c.GetJob(context.TODO(), args[0])
if err != nil {
return fmt.Errorf("failed to get LDAP sync job: %w", err)
}
printJob(job)
return nil
}
func ldapJobShowCompletionF(ctx context.Context, c client.Client, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return fetchAndComplete(
func(ctx context.Context, c client.Client, page, perPage int) ([]*model.Job, *model.Response, error) {
return c.GetJobsByType(ctx, model.JobTypeLdapSync, page, perPage)
},
func(t *model.Job) []string { return []string{t.Id} },
)(ctx, c, cmd, args, toComplete)
}

View File

@ -52,7 +52,7 @@ func (s *MmctlE2ETestSuite) TestLdapSyncCmd() {
s.SetupEnterpriseTestHelper().InitBasic()
configForLdap(s.th)
s.Run("MM-T3971 Should not allow regular user to sync LDAP groups", func() {
s.Run("MM-T3971 Should not allow regular user to start LDAP sync job", func() {
printer.Clean()
err := ldapSyncCmdF(s.th.Client, &cobra.Command{}, nil)
@ -61,7 +61,7 @@ func (s *MmctlE2ETestSuite) TestLdapSyncCmd() {
s.Require().Len(printer.GetErrorLines(), 0)
})
s.RunForSystemAdminAndLocal("MM-T2529 Should sync LDAP groups", func(c client.Client) {
s.RunForSystemAdminAndLocal("MM-T2529 Should start LDAP sync job", func(c client.Client) {
printer.Clean()
jobs, appErr := s.th.App.GetJobsByTypePage(s.th.Context, model.JobTypeLdapSync, 0, 100)
@ -127,3 +127,108 @@ func (s *MmctlE2ETestSuite) TestLdapIDMigrateCmd() {
s.Require().Equal("Dev1", *updatedUser.AuthData)
})
}
func (s *MmctlE2ETestSuite) TestLdapJobListCmd() {
s.SetupEnterpriseTestHelper().InitBasic()
configForLdap(s.th)
s.Run("Should not allow regular user to list LDAP groups", func() {
printer.Clean()
err := ldapJobListCmdF(s.th.Client, &cobra.Command{}, nil)
s.Require().Error(err)
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.RunForSystemAdminAndLocal("No LDAP jobs", func(c client.Client) {
printer.Clean()
cmd := &cobra.Command{}
cmd.Flags().Int("page", 0, "")
cmd.Flags().Int("per-page", 200, "")
cmd.Flags().Bool("all", false, "")
err := ldapJobListCmdF(c, cmd, nil)
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), 1)
s.Require().Empty(printer.GetErrorLines())
s.Equal("No jobs found", printer.GetLines()[0])
})
s.RunForSystemAdminAndLocal("get some LDAP sync jobs", func(c client.Client) {
printer.Clean()
cmd := &cobra.Command{}
perPage := 2
cmd.Flags().Int("page", 0, "")
cmd.Flags().Int("per-page", perPage, "")
cmd.Flags().Bool("all", false, "")
_, appErr := s.th.App.CreateJob(s.th.Context, &model.Job{
Type: model.JobTypeLdapSync,
})
s.Require().Nil(appErr)
time.Sleep(time.Millisecond)
job2, appErr := s.th.App.CreateJob(s.th.Context, &model.Job{
Type: model.JobTypeLdapSync,
})
s.Require().Nil(appErr)
time.Sleep(time.Millisecond)
job3, appErr := s.th.App.CreateJob(s.th.Context, &model.Job{
Type: model.JobTypeLdapSync,
})
s.Require().Nil(appErr)
err := ldapJobListCmdF(c, cmd, nil)
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), perPage)
s.Require().Empty(printer.GetErrorLines())
s.Require().Equal(job3, printer.GetLines()[0].(*model.Job))
s.Require().Equal(job2, printer.GetLines()[1].(*model.Job))
})
}
func (s *MmctlE2ETestSuite) TestLdapJobShowCmdF() {
s.SetupEnterpriseTestHelper().InitBasic()
configForLdap(s.th)
job, appErr := s.th.App.CreateJob(s.th.Context, &model.Job{
Type: model.JobTypeLdapSync,
})
s.Require().Nil(appErr)
time.Sleep(time.Millisecond)
s.Run("no permissions", func() {
printer.Clean()
err := ldapJobShowCmdF(s.th.Client, &cobra.Command{}, []string{job.Id})
s.Require().EqualError(err, "failed to get LDAP sync job: You do not have the appropriate permissions.")
s.Require().Empty(printer.GetLines())
s.Require().Empty(printer.GetErrorLines())
})
s.RunForSystemAdminAndLocal("not found", func(c client.Client) {
printer.Clean()
err := ldapJobShowCmdF(c, &cobra.Command{}, []string{model.NewId()})
s.Require().ErrorContains(err, "failed to get LDAP sync job: Unable to get the job.")
s.Require().Empty(printer.GetLines())
s.Require().Empty(printer.GetErrorLines())
})
s.RunForSystemAdminAndLocal("found", func(c client.Client) {
printer.Clean()
err := ldapJobShowCmdF(c, &cobra.Command{}, []string{job.Id})
s.Require().Nil(err)
s.Require().Empty(printer.GetErrorLines())
s.Require().Len(printer.GetLines(), 1)
s.Require().Equal(job, printer.GetLines()[0].(*model.Job))
})
}

View File

@ -5,14 +5,14 @@ package commands
import (
"context"
"fmt"
"net/http"
"github.com/mattermost/mattermost/server/public/model"
"github.com/pkg/errors"
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
"github.com/spf13/cobra"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
)
func (s *MmctlUnitTestSuite) TestLdapSyncCmd() {
@ -114,3 +114,157 @@ func (s *MmctlUnitTestSuite) TestLdapMigrateID() {
s.Require().Len(printer.GetLines(), 0)
})
}
func (s *MmctlUnitTestSuite) TestLdapJobListCmdF() {
s.Run("no LDAP jobs", func() {
printer.Clean()
var mockJobs []*model.Job
cmd := &cobra.Command{}
perPage := 10
cmd.Flags().Int("page", 0, "")
cmd.Flags().Int("per-page", perPage, "")
cmd.Flags().Bool("all", false, "")
s.client.
EXPECT().
GetJobsByType(context.TODO(), model.JobTypeLdapSync, 0, perPage).
Return(mockJobs, &model.Response{}, nil).
Times(1)
err := ldapJobListCmdF(s.client, cmd, nil)
s.Require().Nil(err)
s.Len(printer.GetLines(), 1)
s.Empty(printer.GetErrorLines())
s.Equal("No jobs found", printer.GetLines()[0])
})
s.Run("some LDAP jobs", func() {
printer.Clean()
mockJobs := []*model.Job{
{
Id: model.NewId(),
},
{
Id: model.NewId(),
},
{
Id: model.NewId(),
},
}
cmd := &cobra.Command{}
perPage := 3
cmd.Flags().Int("page", 0, "")
cmd.Flags().Int("per-page", perPage, "")
cmd.Flags().Bool("all", false, "")
s.client.
EXPECT().
GetJobsByType(context.TODO(), model.JobTypeLdapSync, 0, perPage).
Return(mockJobs, &model.Response{}, nil).
Times(1)
err := ldapJobListCmdF(s.client, cmd, nil)
s.Require().Nil(err)
s.Len(printer.GetLines(), len(mockJobs))
s.Empty(printer.GetErrorLines())
for i, line := range printer.GetLines() {
s.Equal(mockJobs[i], line.(*model.Job))
}
})
}
func (s *MmctlUnitTestSuite) TestLdapJobShowCmdF() {
s.Run("not found", func() {
printer.Clean()
jobID := model.NewId()
s.client.
EXPECT().
GetJob(context.TODO(), jobID).
Return(nil, &model.Response{StatusCode: http.StatusNotFound}, errors.New("not found")).
Times(1)
err := ldapJobShowCmdF(s.client, &cobra.Command{}, []string{jobID})
s.Require().NotNil(err)
s.Empty(printer.GetLines())
s.Empty(printer.GetErrorLines())
})
s.Run("found", func() {
printer.Clean()
mockJob := &model.Job{
Id: model.NewId(),
}
s.client.
EXPECT().
GetJob(context.TODO(), mockJob.Id).
Return(mockJob, &model.Response{}, nil).
Times(1)
err := ldapJobShowCmdF(s.client, &cobra.Command{}, []string{mockJob.Id})
s.Require().Nil(err)
s.Len(printer.GetLines(), 1)
s.Empty(printer.GetErrorLines())
s.Equal(mockJob, printer.GetLines()[0].(*model.Job))
})
s.Run("shell completion", func() {
s.Run("no match for empty argument", func() {
r, dir := ldapJobShowCompletionF(context.Background(), s.client, nil, nil, "")
s.Equal(cobra.ShellCompDirectiveNoFileComp, dir)
s.Equal([]string{}, r)
})
s.Run("one element matches", func() {
mockJobs := []*model.Job{
{
Id: "0_id",
},
{
Id: "1_id",
},
{
Id: "2_id",
},
}
s.client.
EXPECT().
GetJobsByType(context.Background(), model.JobTypeLdapSync, 0, DefaultPageSize).
Return(mockJobs, &model.Response{}, nil).
Times(1)
r, dir := ldapJobShowCompletionF(context.Background(), s.client, nil, nil, "1")
s.Equal(cobra.ShellCompDirectiveNoFileComp, dir)
s.Equal([]string{"1_id"}, r)
})
s.Run("more elements then the limit match", func() {
var mockJobs []*model.Job
for i := 0; i < 100; i++ {
mockJobs = append(mockJobs, &model.Job{
Id: fmt.Sprintf("id_%d", i),
})
}
var expected []string
for i := 0; i < shellCompletionMaxItems; i++ {
expected = append(expected, fmt.Sprintf("id_%d", i))
}
s.client.
EXPECT().
GetJobsByType(context.Background(), model.JobTypeLdapSync, 0, DefaultPageSize).
Return(mockJobs, &model.Response{}, nil).
Times(1)
r, dir := ldapJobShowCompletionF(context.Background(), s.client, nil, nil, "id_")
s.Equal(cobra.ShellCompDirectiveNoFileComp, dir)
s.Equal(expected, r)
})
})
}

View File

@ -38,5 +38,6 @@ SEE ALSO
* `mmctl <mmctl.rst>`_ - Remote client for the Open Source, self-hosted Slack-alternative
* `mmctl ldap idmigrate <mmctl_ldap_idmigrate.rst>`_ - Migrate LDAP IdAttribute to new value
* `mmctl ldap job <mmctl_ldap_job.rst>`_ - List and show LDAP sync jobs
* `mmctl ldap sync <mmctl_ldap_sync.rst>`_ - Synchronize now

View File

@ -0,0 +1,42 @@
.. _mmctl_ldap_job:
mmctl ldap job
--------------
List and show LDAP sync jobs
Synopsis
~~~~~~~~
List and show LDAP sync jobs
Options
~~~~~~~
::
-h, --help help for job
Options inherited from parent commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
--config string path to the configuration file (default "$XDG_CONFIG_HOME/mmctl/config")
--disable-pager disables paged output
--insecure-sha1-intermediate allows to use insecure TLS protocols, such as SHA-1
--insecure-tls-version allows to use TLS versions 1.0 and 1.1
--json the output format will be in json format
--local allows communicating with the server through a unix socket
--quiet prevent mmctl to generate output for the commands
--strict will only run commands if the mmctl version matches the server one
--suppress-warnings disables printing warning messages
SEE ALSO
~~~~~~~~
* `mmctl ldap <mmctl_ldap.rst>`_ - LDAP related utilities
* `mmctl ldap job list <mmctl_ldap_job_list.rst>`_ - List LDAP sync jobs
* `mmctl ldap job show <mmctl_ldap_job_show.rst>`_ - Show LDAP sync job

View File

@ -0,0 +1,54 @@
.. _mmctl_ldap_job_list:
mmctl ldap job list
-------------------
List LDAP sync jobs
Synopsis
~~~~~~~~
List LDAP sync jobs
::
mmctl ldap job list [flags]
Examples
~~~~~~~~
::
ldap job list
Options
~~~~~~~
::
--all Fetch all import jobs. --page flag will be ignore if provided
-h, --help help for list
--page int Page number to fetch for the list of import jobs
--per-page int Number of import jobs to be fetched (default 200)
Options inherited from parent commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
--config string path to the configuration file (default "$XDG_CONFIG_HOME/mmctl/config")
--disable-pager disables paged output
--insecure-sha1-intermediate allows to use insecure TLS protocols, such as SHA-1
--insecure-tls-version allows to use TLS versions 1.0 and 1.1
--json the output format will be in json format
--local allows communicating with the server through a unix socket
--quiet prevent mmctl to generate output for the commands
--strict will only run commands if the mmctl version matches the server one
--suppress-warnings disables printing warning messages
SEE ALSO
~~~~~~~~
* `mmctl ldap job <mmctl_ldap_job.rst>`_ - List and show LDAP sync jobs

View File

@ -0,0 +1,51 @@
.. _mmctl_ldap_job_show:
mmctl ldap job show
-------------------
Show LDAP sync job
Synopsis
~~~~~~~~
Show LDAP sync job
::
mmctl ldap job show [ldapJobID] [flags]
Examples
~~~~~~~~
::
import ldap show f3d68qkkm7n8xgsfxwuo498rah
Options
~~~~~~~
::
-h, --help help for show
Options inherited from parent commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
--config string path to the configuration file (default "$XDG_CONFIG_HOME/mmctl/config")
--disable-pager disables paged output
--insecure-sha1-intermediate allows to use insecure TLS protocols, such as SHA-1
--insecure-tls-version allows to use TLS versions 1.0 and 1.1
--json the output format will be in json format
--local allows communicating with the server through a unix socket
--quiet prevent mmctl to generate output for the commands
--strict will only run commands if the mmctl version matches the server one
--suppress-warnings disables printing warning messages
SEE ALSO
~~~~~~~~
* `mmctl ldap job <mmctl_ldap_job.rst>`_ - List and show LDAP sync jobs