From 23b967f569c895122abc0a40e9e4475cebc04a50 Mon Sep 17 00:00:00 2001 From: Jenson Date: Wed, 16 Jan 2019 17:48:17 +0530 Subject: [PATCH] Add create schedule functionality to client --- cmd/root.go | 17 +++++++- cmd/root_test.go | 1 + cmd/schedule/create/create.go | 67 ++++++++++++++++++++++++++++++ cmd/schedule/schedule.go | 23 +++++++++++ cmd/version/version.go | 2 +- daemon/client.go | 53 ++++++++++++++++++++++++ daemon/client_mock.go | 5 +++ daemon/client_test.go | 77 +++++++++++++++++++++++++++++++++++ 8 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 cmd/schedule/create/create.go create mode 100644 cmd/schedule/schedule.go diff --git a/cmd/root.go b/cmd/root.go index 9ce01c64..2895cf47 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,8 +2,9 @@ package cmd import ( "fmt" + "github.com/gojektech/proctor/cmd/schedule" + "github.com/gojektech/proctor/cmd/schedule/create" "os" - "github.com/gojektech/proctor/cmd/config" "github.com/gojektech/proctor/cmd/config/view" "github.com/gojektech/proctor/cmd/description" @@ -43,6 +44,20 @@ func Execute(printer io.Printer, proctorDClient daemon.Client) { rootCmd.AddCommand(configCmd) configCmd.AddCommand(configShowCmd) + scheduleCmd := schedule.NewCmd(printer) + rootCmd.AddCommand(scheduleCmd) + scheduleCreateCmd := create.NewCmd(printer, proctorDClient) + scheduleCmd.AddCommand(scheduleCreateCmd) + + var Time, NotifyEmails, Tags string + + scheduleCreateCmd.PersistentFlags().StringVarP(&Time, "time", "t", "", "Schedule time") + scheduleCreateCmd.MarkFlagRequired("time") + scheduleCreateCmd.PersistentFlags().StringVarP(&NotifyEmails, "notify", "n", "", "Notifier Email ID's") + scheduleCreateCmd.MarkFlagRequired("notify") + scheduleCreateCmd.PersistentFlags().StringVarP(&Tags, "tags", "T", "", "Tags") + scheduleCreateCmd.MarkFlagRequired("tags") + if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/root_test.go b/cmd/root_test.go index 87af9692..99cd2472 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -35,4 +35,5 @@ func TestRootCmdSubCommands(t *testing.T) { assert.True(t, contains(rootCmd.Commands(), "list")) assert.True(t, contains(rootCmd.Commands(), "config")) assert.True(t, contains(rootCmd.Commands(), "version")) + assert.True(t,contains(rootCmd.Commands(), "schedule")) } diff --git a/cmd/schedule/create/create.go b/cmd/schedule/create/create.go new file mode 100644 index 00000000..f68c1e02 --- /dev/null +++ b/cmd/schedule/create/create.go @@ -0,0 +1,67 @@ +package create + +import ( + "fmt" + "github.com/fatih/color" + "github.com/gojektech/proctor/daemon" + "github.com/gojektech/proctor/io" + "github.com/spf13/cobra" + "strings" +) + +func NewCmd(printer io.Printer, proctorDClient daemon.Client) *cobra.Command { + return &cobra.Command{ + Use: "create", + Short: "Create scheduled jobs", + Long: "This command helps to create scheduled jobs", + Example: fmt.Sprintf("proctor schedule create run-sample -t '0 2 * * *' -n 'username@mail.com' -T 'sample,proctor' ARG_ONE1=foobar"), + Args: cobra.MinimumNArgs(1), + + Run: func(cmd *cobra.Command, args []string) { + procName := args[0] + printer.Println(fmt.Sprintf("%-40s %-100s", "Creating Scheduled Job", procName), color.Reset) + time, err := cmd.Flags().GetString("time") + if err != nil { + printer.Println(err.Error(),color.FgRed) + } + + notificationEmails, err := cmd.Flags().GetString("notify") + if err != nil { + printer.Println(err.Error(),color.FgRed) + } + + tags, err := cmd.Flags().GetString("tags") + if err != nil { + printer.Println(err.Error(),color.FgRed) + } + + jobArgs := make(map[string]string) + if len(args) > 1 { + printer.Println("With Variables", color.FgMagenta) + for _, v := range args[1:] { + arg := strings.Split(v, "=") + + if len(arg) < 2 { + printer.Println(fmt.Sprintf("%-40s %-100s", "\nIncorrect variable format\n", v), color.FgRed) + continue + } + + combinedArgValue := strings.Join(arg[1:], "=") + jobArgs[arg[0]] = combinedArgValue + + printer.Println(fmt.Sprintf("%-40s %-100s", arg[0], combinedArgValue), color.Reset) + } + } else { + printer.Println("With No Variables", color.FgRed) + } + + scheduledJobID, err := proctorDClient.ScheduleJob(procName, tags, time, notificationEmails, jobArgs) + if err != nil { + printer.Println(err.Error(), color.FgRed) + print() + return + } + printer.Println(fmt.Sprintf("Scheduled Job UUID : %s", scheduledJobID), color.FgGreen) + }, + } +} diff --git a/cmd/schedule/schedule.go b/cmd/schedule/schedule.go new file mode 100644 index 00000000..5b124d71 --- /dev/null +++ b/cmd/schedule/schedule.go @@ -0,0 +1,23 @@ +package schedule + +import ( + "fmt" + "github.com/fatih/color" + "github.com/gojektech/proctor/io" + "github.com/spf13/cobra" +) + +func NewCmd(printer io.Printer) *cobra.Command { + return &cobra.Command{ + Use: "schedule", + Short: "Schedule proctor jobs", + Long: "This command helps to maange scheduled proctor jobs", + Example: fmt.Sprintf("proctor schedule help"), + Args: cobra.MinimumNArgs(1), + + Run: func(cmd *cobra.Command, args []string) { + printer.Println(fmt.Sprintf("Print:"), color.FgRed) + }, + } +} + diff --git a/cmd/version/version.go b/cmd/version/version.go index 653f46cf..79e53598 100644 --- a/cmd/version/version.go +++ b/cmd/version/version.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/cobra" ) -const ClientVersion = "v0.4.0" +const ClientVersion = "v0.5.0" func NewCmd(printer io.Printer) *cobra.Command { return &cobra.Command{ diff --git a/daemon/client.go b/daemon/client.go index 0534dba5..b5de2dd8 100644 --- a/daemon/client.go +++ b/daemon/client.go @@ -29,6 +29,7 @@ type Client interface { ExecuteProc(string, map[string]string) (string, error) StreamProcLogs(string) error GetDefinitiveProcExecutionStatus(string) (string, error) + ScheduleJob(string, string, string, string, map[string]string) (string, error) } type client struct { @@ -47,6 +48,15 @@ type ProcToExecute struct { Args map[string]string `json:"args"` } +type ScheduleJobPayload struct { + ID string `json:"id"` + Name string `json:"name"` + Tags string `json:"tags"` + Time string `json:"time"` + NotificationEmails string `json:"notification_emails"` + Args map[string]string `json:"args"` +} + func NewClient(printer io.Printer, proctorConfigLoader config.Loader) Client { return &client{ clientVersion: version.ClientVersion, @@ -55,6 +65,49 @@ func NewClient(printer io.Printer, proctorConfigLoader config.Loader) Client { } } +func(c *client) ScheduleJob(name, tags, time, notificationEmails string,jobArgs map[string]string) (string, error){ + err := c.loadProctorConfig() + if err != nil { + return "", err + } + jobPayload := ScheduleJobPayload{ + Name: name, + Tags: tags, + Time: time, + NotificationEmails: notificationEmails, + Args: jobArgs, + } + + requestBody, err := json.Marshal(jobPayload) + if err != nil { + return "", err + } + + client := &http.Client{} + req, err := http.NewRequest("POST", "http://"+c.proctordHost+"/jobs/schedule", bytes.NewReader(requestBody)) + req.Header.Add("Content-Type", "application/json") + req.Header.Add(utility.UserEmailHeaderKey, c.emailId) + req.Header.Add(utility.AccessTokenHeaderKey, c.accessToken) + req.Header.Add(utility.ClientVersionHeaderKey, c.clientVersion) + resp, err := client.Do(req) + + if err != nil { + return "", buildNetworkError(err) + } + + defer resp.Body.Close() + if resp.StatusCode != http.StatusCreated { + body, _ := ioutil.ReadAll(resp.Body) + bodyString := string(body) + return "", errors.New(bodyString) + } + + var scheduledJob ScheduleJobPayload + err = json.NewDecoder(resp.Body).Decode(&scheduledJob) + + return scheduledJob.ID, err +} + func (c *client) loadProctorConfig() error { proctorConfig, err := c.proctorConfigLoader.Load() if err != (config.ConfigError{}) { diff --git a/daemon/client_mock.go b/daemon/client_mock.go index 54d55038..57578d90 100644 --- a/daemon/client_mock.go +++ b/daemon/client_mock.go @@ -28,3 +28,8 @@ func (m *MockClient) GetDefinitiveProcExecutionStatus(name string) (string, erro args := m.Called(name) return args.Get(0).(string), args.Error(1) } + +func (m *MockClient) ScheduleJob(name, tags, time, notificationEmails string,jobArgs map[string]string) (string, error) { + args := m.Called(name, tags, time, notificationEmails, jobArgs) + return args.Get(0).(string), args.Error(1) +} \ No newline at end of file diff --git a/daemon/client_test.go b/daemon/client_test.go index 42e7ca45..6e6e27bf 100644 --- a/daemon/client_test.go +++ b/daemon/client_test.go @@ -295,6 +295,83 @@ func (s *ClientTestSuite) TestExecuteProc() { s.mockConfigLoader.AssertExpectations(t) } +func (s *ClientTestSuite) TestSuccessScheduledJob() { + t := s.T() + + proctorConfig := config.ProctorConfig{Host: "proctor.example.com", Email: "proctor@example.com", AccessToken: "access-token"} + expectedProcResponse := "8965fce9-5025-43b3-b21c-920c5ff41cd9" + procName := "run-sample" + time := "*/1 * * * *" + notificationEmails := "user@mail.com" + tags := "db,backup" + procArgs := map[string]string{"ARG_ONE": "sample-value"} + + body := `{"id":"8965fce9-5025-43b3-b21c-920c5ff41cd9","name":"run-sample","args":{"ARG_ONE":"sample-value"},"notification_emails":"user@mail.com","time":"*/1 * * * *","tags":"db,backup"}` + + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + httpmock.RegisterStubRequest( + httpmock.NewStubRequest( + "POST", + "http://"+proctorConfig.Host+"/jobs/schedule", + func(req *http.Request) (*http.Response, error) { + return httpmock.NewStringResponse(201, body), nil + }, + ).WithHeader( + &http.Header{ + utility.UserEmailHeaderKey: []string{"proctor@example.com"}, + utility.AccessTokenHeaderKey: []string{"access-token"}, + utility.ClientVersionHeaderKey: []string{version.ClientVersion}, + }, + ), + ) + + s.mockConfigLoader.On("Load").Return(proctorConfig, config.ConfigError{}).Once() + + executeProcResponse, err := s.testClient.ScheduleJob(procName,tags,time,notificationEmails,procArgs) + + assert.NoError(t, err) + assert.Equal(t, expectedProcResponse, executeProcResponse) + s.mockConfigLoader.AssertExpectations(t) +} + +func (s *ClientTestSuite) TestSchedulingAlreadyExistedScheduledJob() { + t := s.T() + + proctorConfig := config.ProctorConfig{Host: "proctor.example.com", Email: "proctor@example.com", AccessToken: "access-token"} + procName := "run-sample" + time := "*/1 * * * *" + notificationEmails := "user@mail.com" + tags := "db,backup" + procArgs := map[string]string{"ARG_ONE": "sample-value"} + + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + httpmock.RegisterStubRequest( + httpmock.NewStubRequest( + "POST", + "http://"+proctorConfig.Host+"/jobs/schedule", + func(req *http.Request) (*http.Response, error) { + return httpmock.NewStringResponse(409, "provided duplicate combination of job name and args for scheduling"), nil + }, + ).WithHeader( + &http.Header{ + utility.UserEmailHeaderKey: []string{"proctor@example.com"}, + utility.AccessTokenHeaderKey: []string{"access-token"}, + utility.ClientVersionHeaderKey: []string{version.ClientVersion}, + }, + ), + ) + + s.mockConfigLoader.On("Load").Return(proctorConfig, config.ConfigError{}).Once() + + _, err := s.testClient.ScheduleJob(procName,tags,time,notificationEmails,procArgs) + assert.Equal(t,"provided duplicate combination of job name and args for scheduling", err.Error()) + s.mockConfigLoader.AssertExpectations(t) +} + func (s *ClientTestSuite) TestExecuteProcInternalServerError() { t := s.T() proctorConfig := config.ProctorConfig{Host: "proctor.example.com", Email: "proctor@example.com", AccessToken: "access-token"}