From 823d8f53ca60fdeda3ee73fbaa4a0dbc1728c7b9 Mon Sep 17 00:00:00 2001 From: ClaudioGisch Date: Wed, 9 Oct 2024 10:45:21 -0300 Subject: [PATCH] feat: job with dockerfile (#233) * feat: job with dockerfile support * test: job with dockerfile support * lint: remove unused variable * fix: job deploy exemples * chore: warning for job update with image --- tsuru/client/build.go | 10 +- tsuru/client/deploy.go | 186 +++++++++++++++ tsuru/client/deploy_test.go | 276 +++++++++++++++++++++++ tsuru/client/jobs.go | 23 +- tsuru/client/jobs_test.go | 19 +- tsuru/client/testdata/deploy5/Dockerfile | 3 + tsuru/client/testdata/deploy5/job.sh | 1 + tsuru/main.go | 1 + 8 files changed, 501 insertions(+), 18 deletions(-) create mode 100644 tsuru/client/testdata/deploy5/Dockerfile create mode 100644 tsuru/client/testdata/deploy5/job.sh diff --git a/tsuru/client/build.go b/tsuru/client/build.go index 4a3d9c487..ac3998438 100644 --- a/tsuru/client/build.go +++ b/tsuru/client/build.go @@ -190,7 +190,7 @@ func uploadFiles(context *cmd.Context, request *http.Request, buf *safe.Buffer, return nil } -func buildWithContainerFile(appName, path string, filesOnly bool, files []string, stderr io.Writer) (string, io.Reader, error) { +func buildWithContainerFile(resourceName, path string, filesOnly bool, files []string, stderr io.Writer) (string, io.Reader, error) { fi, err := os.Stat(path) if err != nil { return "", nil, fmt.Errorf("failed to stat the file %s: %w", path, err) @@ -200,7 +200,7 @@ func buildWithContainerFile(appName, path string, filesOnly bool, files []string switch { case fi.IsDir(): - path, err = guessingContainerFile(appName, path) + path, err = guessingContainerFile(resourceName, path) if err != nil { return "", nil, fmt.Errorf("failed to guess the container file (can you specify the container file passing --dockerfile ./path/to/Dockerfile?): %w", err) } @@ -230,10 +230,10 @@ func buildWithContainerFile(appName, path string, filesOnly bool, files []string return string(containerfile), &buildContext, nil } -func guessingContainerFile(app, dir string) (string, error) { +func guessingContainerFile(resourceName, dir string) (string, error) { validNames := []string{ - fmt.Sprintf("Dockerfile.%s", app), - fmt.Sprintf("Containerfile.%s", app), + fmt.Sprintf("Dockerfile.%s", resourceName), + fmt.Sprintf("Containerfile.%s", resourceName), "Dockerfile.tsuru", "Containerfile.tsuru", "Dockerfile", diff --git a/tsuru/client/deploy.go b/tsuru/client/deploy.go index 0e4d65977..51ec3ae1c 100644 --- a/tsuru/client/deploy.go +++ b/tsuru/client/deploy.go @@ -352,6 +352,7 @@ func (c *AppDeploy) Cancel(ctx cmd.Context) error { } c.m.Lock() defer c.m.Unlock() + ctx.RawOutput() if c.eventID == "" { return errors.New("event ID not available yet") } @@ -573,3 +574,188 @@ func (c *deployVersionArgs) values(values url.Values) { values.Set("override-versions", strconv.FormatBool(c.overrideVersions)) } } + +var _ cmd.Cancelable = &JobDeploy{} + +type JobDeploy struct { + jobName string + image string + message string + dockerfile string + eventID string + fs *gnuflag.FlagSet + m sync.Mutex +} + +func (c *JobDeploy) Flags() *gnuflag.FlagSet { + if c.fs == nil { + c.fs = gnuflag.NewFlagSet("", gnuflag.ExitOnError) + c.fs.StringVar(&c.jobName, "job", "", "The name of the job.") + c.fs.StringVar(&c.jobName, "j", "", "The name of the job.") + image := "The image to deploy in job" + c.fs.StringVar(&c.image, "image", "", image) + c.fs.StringVar(&c.image, "i", "", image) + message := "A message describing this deploy" + c.fs.StringVar(&c.message, "message", "", message) + c.fs.StringVar(&c.message, "m", "", message) + c.fs.StringVar(&c.dockerfile, "dockerfile", "", "Container file") + } + return c.fs +} + +func (c *JobDeploy) Info() *cmd.Info { + return &cmd.Info{ + Name: "job-deploy", + Usage: "job deploy [--job ] [--image ] [--dockerfile ] [--message ]", + Desc: `Deploy the source code and/or configurations to a Job on Tsuru. + +Files specified in the ".tsuruignore" file are skipped - similar to ".gitignore". It also honors ".dockerignore" file if deploying with container file (--dockerfile). + +Examples: + To deploy using a container image: + $ tsuru job deploy -j --image registry.example.com/my-company/my-job:v42 + + To deploy using container file ("docker build" mode): + Sending the the current directory as container build context - uses Dockerfile file as container image instructions: + $ tsuru job deploy -j --dockerfile . + + Sending a specific container file and specific directory as container build context: + $ tsuru job deploy -j --dockerfile ./Dockerfile.other ./other/ +`, + MinArgs: 0, + } +} + +func (c *JobDeploy) Run(context *cmd.Context) error { + context.RawOutput() + + if c.jobName == "" { + return errors.New(`The name of the job is required. + +Use the --job/-j flag to specify it. + +`) + } + + if c.image == "" && c.dockerfile == "" { + return errors.New("You should provide at least one between Docker image name or Dockerfile to deploy.\n") + } + + if c.image != "" && len(context.Args) > 0 { + return errors.New("You can't deploy files and docker image at the same time.\n") + } + + if c.image != "" && c.dockerfile != "" { + return errors.New("You can't deploy container image and container file at same time.\n") + } + + values := url.Values{} + + origin := "job-deploy" + if c.image != "" { + origin = "image" + } + values.Set("origin", origin) + + if c.message != "" { + values.Set("message", c.message) + } + + u, err := config.GetURLVersion("1.23", "/jobs/"+c.jobName+"/deploy") + if err != nil { + return err + } + + body := safe.NewBuffer(nil) + request, err := http.NewRequest("POST", u, body) + if err != nil { + return err + } + + buf := safe.NewBuffer(nil) + + c.m.Lock() + respBody := prepareUploadStreams(context, buf) + c.m.Unlock() + + var archive io.Reader + + if c.image != "" { + fmt.Fprintln(context.Stdout, "Deploying container image...") + values.Set("image", c.image) + } + + if c.dockerfile != "" { + fmt.Fprintln(context.Stdout, "Deploying with Dockerfile...") + + var dockerfile string + dockerfile, archive, err = buildWithContainerFile(c.jobName, c.dockerfile, false, context.Args, nil) + if err != nil { + return err + } + + values.Set("dockerfile", dockerfile) + } + + if err = uploadFiles(context, request, buf, body, values, archive); err != nil { + return err + } + + c.m.Lock() + resp, err := tsuruHTTP.AuthenticatedClient.Do(request) + if err != nil { + c.m.Unlock() + return err + } + defer resp.Body.Close() + c.eventID = resp.Header.Get("X-Tsuru-Eventid") + c.m.Unlock() + + var readBuffer [deployOutputBufferSize]byte + var readErr error + for readErr == nil { + var read int + read, readErr = resp.Body.Read(readBuffer[:]) + if read == 0 { + continue + } + c.m.Lock() + written, writeErr := respBody.Write(readBuffer[:read]) + c.m.Unlock() + if written < read { + return fmt.Errorf("short write processing output, read: %d, written: %d", read, written) + } + if writeErr != nil { + return fmt.Errorf("error writing response: %v", writeErr) + } + } + if readErr != io.EOF { + return fmt.Errorf("error reading response: %v", readErr) + } + if strings.HasSuffix(buf.String(), "\nOK\n") { + return nil + } + return cmd.ErrAbortCommand +} + +func (c *JobDeploy) Cancel(ctx cmd.Context) error { + apiClient, err := tsuruHTTP.TsuruClientFromEnvironment() + if err != nil { + return err + } + c.m.Lock() + defer c.m.Unlock() + ctx.RawOutput() + if c.eventID == "" { + return errors.New("event ID not available yet") + } + fmt.Fprintln(ctx.Stdout, cmd.Colorfy("Warning: the deploy is still RUNNING in the background!", "red", "", "bold")) + fmt.Fprint(ctx.Stdout, "Are you sure you want to cancel this deploy? (Y/n) ") + var answer string + fmt.Fscanf(ctx.Stdin, "%s", &answer) + if strings.ToLower(answer) != "y" && answer != "" { + return fmt.Errorf("aborted") + } + _, err = apiClient.EventApi.EventCancel(context.Background(), c.eventID, tsuru.EventCancelArgs{Reason: "Canceled on client."}) + return err +} diff --git a/tsuru/client/deploy_test.go b/tsuru/client/deploy_test.go index fe8e15cb7..f098b17c6 100644 --- a/tsuru/client/deploy_test.go +++ b/tsuru/client/deploy_test.go @@ -561,3 +561,279 @@ func (s *S) TestAppDeployRebuild(c *check.C) { c.Assert(called, check.Equals, true) c.Assert(stdout.String(), check.Equals, expectedOut) } + +func (s *S) TestJobDeployRunUsingDockerfile(c *check.C) { + command := JobDeploy{} + err := command.Flags().Parse(true, []string{"-j", "my-job", "--dockerfile", "./testdata/deploy5/"}) + c.Assert(err, check.IsNil) + + ctx := &cmd.Context{Stdout: io.Discard, Stderr: io.Discard, Args: command.Flags().Args()} + + trans := &cmdtest.ConditionalTransport{ + Transport: cmdtest.Transport{Message: "deployed\nOK\n", Status: http.StatusOK}, + CondFunc: func(req *http.Request) bool { + if req.Body != nil { + defer req.Body.Close() + } + c.Assert(req.Header.Get("Content-Type"), check.Matches, "multipart/form-data; boundary=.*") + c.Assert(req.FormValue("dockerfile"), check.Equals, "FROM busybox:1.36.0-glibc\n\nCOPY ./job.sh /usr/local/bin/\n") + + file, _, nerr := req.FormFile("file") + c.Assert(nerr, check.IsNil) + defer file.Close() + files := extractFiles(s.t, c, file) + c.Assert(files, check.DeepEquals, []miniFile{ + {Name: filepath.Join("Dockerfile"), Type: tar.TypeReg, Data: []byte("FROM busybox:1.36.0-glibc\n\nCOPY ./job.sh /usr/local/bin/\n")}, + {Name: filepath.Join("job.sh"), Type: tar.TypeReg, Data: []byte("echo \"My job here is done!\"\n")}, + }) + + return req.Method == "POST" && strings.HasSuffix(req.URL.Path, "/jobs/my-job/deploy") + }, + } + + s.setupFakeTransport(trans) + err = command.Run(ctx) + c.Assert(err, check.IsNil) +} + +func (s *S) TestJobDeployRunUsingImage(c *check.C) { + trans := cmdtest.ConditionalTransport{ + Transport: cmdtest.Transport{Message: "deploy worked\nOK\n", Status: http.StatusOK}, + CondFunc: func(req *http.Request) bool { + if req.Body != nil { + defer req.Body.Close() + } + c.Assert(req.Header.Get("Content-Type"), check.Matches, "application/x-www-form-urlencoded") + c.Assert(req.FormValue("image"), check.Equals, "registr.com/image-to-deploy:latest") + c.Assert(req.FormValue("origin"), check.Equals, "image") + return req.Method == "POST" && strings.HasSuffix(req.URL.Path, "/jobs/my-job/deploy") + }, + } + + s.setupFakeTransport(&trans) + var stdout, stderr bytes.Buffer + context := cmd.Context{ + Stdout: &stdout, + Stderr: &stderr, + } + + cmd := JobDeploy{} + err := cmd.Flags().Parse(true, []string{"-j", "my-job", "-i", "registr.com/image-to-deploy:latest"}) + c.Assert(err, check.IsNil) + + context.Args = cmd.Flags().Args() + err = cmd.Run(&context) + c.Assert(err, check.IsNil) +} + +func (s *S) TestJobDeployRunCancel(c *check.C) { + command := JobDeploy{} + err := command.Flags().Parse(true, []string{"-j", "my-job", "--dockerfile", "./testdata/deploy5/"}) + c.Assert(err, check.IsNil) + + deploy := make(chan struct{}, 1) + trans := cmdtest.MultiConditionalTransport{ + ConditionalTransports: []cmdtest.ConditionalTransport{ + { + Transport: &cmdtest.BodyTransport{ + Status: http.StatusOK, + Headers: map[string][]string{"X-Tsuru-Eventid": {"5aec54d93195b20001194952"}}, + Body: &slowReader{ReadCloser: io.NopCloser(bytes.NewBufferString("deploy worked\nOK\n")), Latency: time.Second * 5}, + }, + CondFunc: func(req *http.Request) bool { + deploy <- struct{}{} + if req.Body != nil { + defer req.Body.Close() + } + c.Assert(req.Header.Get("Content-Type"), check.Matches, "multipart/form-data; boundary=.*") + c.Assert(req.FormValue("dockerfile"), check.Equals, "FROM busybox:1.36.0-glibc\n\nCOPY ./job.sh /usr/local/bin/\n") + + file, _, nerr := req.FormFile("file") + c.Assert(nerr, check.IsNil) + defer file.Close() + files := extractFiles(s.t, c, file) + c.Assert(files, check.DeepEquals, []miniFile{ + {Name: filepath.Join("Dockerfile"), Type: tar.TypeReg, Data: []byte("FROM busybox:1.36.0-glibc\n\nCOPY ./job.sh /usr/local/bin/\n")}, + {Name: filepath.Join("job.sh"), Type: tar.TypeReg, Data: []byte("echo \"My job here is done!\"\n")}, + }) + + return req.Method == "POST" && strings.HasSuffix(req.URL.Path, "/jobs/my-job/deploy") + }, + }, + { + Transport: cmdtest.Transport{Status: http.StatusOK}, + CondFunc: func(req *http.Request) bool { + c.Assert(req.Method, check.Equals, "POST") + c.Assert(req.URL.Path, check.Equals, "/1.1/events/5aec54d93195b20001194952/cancel") + return true + }, + }, + }, + } + + s.setupFakeTransport(&trans) + var stdout, stderr bytes.Buffer + + ctx := cmd.Context{ + Stdout: &stdout, + Stderr: &stderr, + Stdin: bytes.NewReader([]byte("y")), + Args: command.Flags().Args(), + } + + go func() { + err = command.Run(&ctx) + c.Assert(err, check.IsNil) + }() + + <-deploy + + err = command.Cancel(ctx) + c.Assert(err, check.IsNil) +} + +func (s *S) TestJobDeployRunWithMessage(c *check.C) { + command := JobDeploy{} + err := command.Flags().Parse(true, []string{"-j", "my-job", "--dockerfile", "./testdata/deploy5/", "-m", "my-job deploy"}) + c.Assert(err, check.IsNil) + + trans := cmdtest.ConditionalTransport{ + Transport: cmdtest.Transport{Message: "deploy worked\nOK\n", Status: http.StatusOK}, + CondFunc: func(req *http.Request) bool { + if req.Body != nil { + defer req.Body.Close() + } + c.Assert(req.Header.Get("Content-Type"), check.Matches, "multipart/form-data; boundary=.*") + c.Assert(req.FormValue("dockerfile"), check.Equals, "FROM busybox:1.36.0-glibc\n\nCOPY ./job.sh /usr/local/bin/\n") + c.Assert(req.FormValue("message"), check.Equals, "my-job deploy") + + file, _, nerr := req.FormFile("file") + c.Assert(nerr, check.IsNil) + defer file.Close() + files := extractFiles(s.t, c, file) + c.Assert(files, check.DeepEquals, []miniFile{ + {Name: filepath.Join("Dockerfile"), Type: tar.TypeReg, Data: []byte("FROM busybox:1.36.0-glibc\n\nCOPY ./job.sh /usr/local/bin/\n")}, + {Name: filepath.Join("job.sh"), Type: tar.TypeReg, Data: []byte("echo \"My job here is done!\"\n")}, + }) + + return req.Method == "POST" && strings.HasSuffix(req.URL.Path, "/jobs/my-job/deploy") + }, + } + + s.setupFakeTransport(&trans) + var stdout, stderr bytes.Buffer + ctx := cmd.Context{ + Stdout: &stdout, + Stderr: &stderr, + Args: command.Flags().Args(), + } + + err = command.Run(&ctx) + c.Assert(err, check.IsNil) +} + +func (s *S) TestJobDeployAuthNotOK(c *check.C) { + command := JobDeploy{} + err := command.Flags().Parse(true, []string{"-j", "my-job", "--dockerfile", "./testdata/deploy5/"}) + c.Assert(err, check.IsNil) + + trans := cmdtest.ConditionalTransport{ + Transport: cmdtest.Transport{Message: "Forbidden", Status: http.StatusForbidden}, + CondFunc: func(req *http.Request) bool { + return req.Method == "POST" && strings.HasSuffix(req.URL.Path, "/jobs/my-job/deploy") + }, + } + + s.setupFakeTransport(&trans) + var stdout, stderr bytes.Buffer + context := cmd.Context{ + Stdout: &stdout, + Stderr: &stderr, + Args: command.Flags().Args(), + } + c.Assert(err, check.IsNil) + + context.Args = command.Flags().Args() + err = command.Run(&context) + c.Assert(err, check.ErrorMatches, ".* Forbidden") +} + +func (s *S) TestJobDeployRunNotOK(c *check.C) { + command := JobDeploy{} + err := command.Flags().Parse(true, []string{"-j", "my-job", "--dockerfile", "./testdata/deploy5/"}) + c.Assert(err, check.IsNil) + + trans := cmdtest.Transport{Message: "deploy worked\n", Status: http.StatusOK} + + s.setupFakeTransport(&trans) + var stdout, stderr bytes.Buffer + context := cmd.Context{ + Stdout: &stdout, + Stderr: &stderr, + Args: command.Flags().Args(), + } + c.Assert(err, check.IsNil) + + context.Args = command.Flags().Args() + err = command.Run(&context) + c.Assert(err, check.Equals, cmd.ErrAbortCommand) +} + +func (s *S) TestJobDeployRunFileNotFound(c *check.C) { + command := JobDeploy{} + err := command.Flags().Parse(true, []string{"-j", "my-job", "--dockerfile", "/tmp/aint/no/way/this/exists"}) + c.Assert(err, check.IsNil) + + var stdout, stderr bytes.Buffer + context := cmd.Context{ + Stdout: &stdout, + Stderr: &stderr, + Args: []string{"/tmp/something/that/doesn't/really/exist/im/sure", "-a", "secret"}, + } + trans := cmdtest.Transport{Message: "OK\n", Status: http.StatusOK} + + s.setupFakeTransport(&trans) + c.Assert(err, check.IsNil) + + context.Args = command.Flags().Args() + err = command.Run(&context) + c.Assert(err, check.NotNil) +} + +func (s *S) TestJobDeployRunWithoutArgsAndImage(c *check.C) { + command := JobDeploy{} + err := command.Flags().Parse(true, []string{"-j", "my-job"}) + c.Assert(err, check.IsNil) + + ctx := &cmd.Context{Stdout: io.Discard, Stderr: io.Discard, Args: command.Flags().Args()} + s.setupFakeTransport(&cmdtest.Transport{Status: http.StatusInternalServerError}) + + err = command.Run(ctx) + c.Assert(err, check.NotNil) + c.Assert(err.Error(), check.Equals, "You should provide at least one between Docker image name or Dockerfile to deploy.\n") +} + +func (s *S) TestJobDeployRunRequestFailure(c *check.C) { + command := JobDeploy{} + err := command.Flags().Parse(true, []string{"-j", "my-job", "--dockerfile", "./testdata/deploy5/"}) + c.Assert(err, check.IsNil) + + trans := cmdtest.Transport{Message: "job not found\n", Status: http.StatusNotFound} + s.setupFakeTransport(&trans) + + ctx := &cmd.Context{Stdout: io.Discard, Stderr: io.Discard, Args: command.Flags().Args()} + err = command.Run(ctx) + c.Assert(err, check.ErrorMatches, ".* job not found\n") +} + +func (s *S) TestDeployRunDockerfileAndDockerImage(c *check.C) { + command := JobDeploy{} + err := command.Flags().Parse(true, []string{"-j", "my-job", "-i", "registr.com/image-to-deploy:latest", "--dockerfile", "."}) + c.Assert(err, check.IsNil) + + ctx := &cmd.Context{Stdout: io.Discard, Stderr: io.Discard, Args: command.Flags().Args()} + s.setupFakeTransport(&cmdtest.Transport{Status: http.StatusInternalServerError}) + + err = command.Run(ctx) + c.Assert(err, check.ErrorMatches, "You can't deploy container image and container file at same time.\n") +} diff --git a/tsuru/client/jobs.go b/tsuru/client/jobs.go index e278ad27b..2af100a2a 100644 --- a/tsuru/client/jobs.go +++ b/tsuru/client/jobs.go @@ -79,7 +79,7 @@ The [[--tag]] parameter sets a tag to your job. You can set multiple [[--tag]] p The [[--max-running-time]] sets maximum amount of time (in seconds) that the job can run. If the job exceeds this limit, it will be automatically stopped. If this parameter is not informed, default value is 3600s`, - MinArgs: 2, + MinArgs: 1, } } @@ -140,13 +140,21 @@ func (c *JobCreate) Run(ctx *cmd.Context) error { if c.manual && c.schedule != "" { return errors.New("cannot set both manual job and schedule options") } + + var image string + var parsedCommands []string jobName := ctx.Args[0] - image := ctx.Args[1] - commands := ctx.Args[2:] - parsedCommands, err := parseJobCommands(commands) - if err != nil { - return err + if len(ctx.Args) > 1 { + fmt.Fprintf(ctx.Stdout, "Job creation with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\n") + image = ctx.Args[1] + commands := ctx.Args[2:] + + parsedCommands, err = parseJobCommands(commands) + if err != nil { + return err + } } + var activeDeadlineSecondsResult *int64 if c.fs != nil { c.fs.Visit(func(f *gnuflag.Flag) { @@ -558,6 +566,9 @@ func (c *JobUpdate) Run(ctx *cmd.Context) error { if c.manual && c.schedule != "" { return errors.New("cannot set both manual job and schedule options") } + if c.image != "" { + fmt.Fprintf(ctx.Stdout, "Job update with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\n") + } var jobUpdateCommands []string if len(ctx.Args) > 1 { jobUpdateCommands, err = parseJobCommands(ctx.Args[1:]) diff --git a/tsuru/client/jobs_test.go b/tsuru/client/jobs_test.go index 819be655a..7ccd94b32 100644 --- a/tsuru/client/jobs_test.go +++ b/tsuru/client/jobs_test.go @@ -25,7 +25,8 @@ func (s *S) TestJobCreate(c *check.C) { Stdout: &stdout, Stderr: &stderr, } - expected := "Job created\nUse \"tsuru job info loucoAbreu\" to check the status of the job\n" + expected := "Job creation with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\n" + + "Job created\nUse \"tsuru job info loucoAbreu\" to check the status of the job\n" trans := cmdtest.ConditionalTransport{ Transport: cmdtest.Transport{Message: `{"jobName":"loucoAbreu","status":"success"}`, Status: http.StatusCreated}, CondFunc: func(r *http.Request) bool { @@ -66,7 +67,8 @@ func (s *S) TestJobCreateWithEmptyCommand(c *check.C) { Stdout: &stdout, Stderr: &stderr, } - expected := "Job created\nUse \"tsuru job info job-using-entrypoint\" to check the status of the job\n" + expected := "Job creation with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\n" + + "Job created\nUse \"tsuru job info job-using-entrypoint\" to check the status of the job\n" trans := cmdtest.ConditionalTransport{ Transport: cmdtest.Transport{Message: `{"jobName":"job-using-entrypoint","status":"success"}`, Status: http.StatusCreated}, CondFunc: func(r *http.Request) bool { @@ -105,7 +107,8 @@ func (s *S) TestJobCreateManual(c *check.C) { Stdout: &stdout, Stderr: &stderr, } - expected := "Job created\nUse \"tsuru job info daft-punk\" to check the status of the job\n" + expected := "Job creation with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\n" + + "Job created\nUse \"tsuru job info daft-punk\" to check the status of the job\n" trans := cmdtest.ConditionalTransport{ Transport: cmdtest.Transport{Message: `{"jobName":"daft-punk","status":"success"}`, Status: http.StatusCreated}, CondFunc: func(r *http.Request) bool { @@ -147,7 +150,8 @@ func (s *S) TestJobCreateParseMultipleCommands(c *check.C) { Stdout: &stdout, Stderr: &stderr, } - expected := "Job created\nUse \"tsuru job info NiltonSantos\" to check the status of the job\n" + expected := "Job creation with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\n" + + "Job created\nUse \"tsuru job info NiltonSantos\" to check the status of the job\n" trans := cmdtest.ConditionalTransport{ Transport: cmdtest.Transport{Message: `{"jobName":"loucoAbreu","status":"success"}`, Status: http.StatusCreated}, CondFunc: func(r *http.Request) bool { @@ -176,7 +180,8 @@ func (s *S) TestJobCreateParseJSON(c *check.C) { Stdout: &stdout, Stderr: &stderr, } - expected := "Job created\nUse \"tsuru job info NiltonSantos\" to check the status of the job\n" + expected := "Job creation with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\n" + + "Job created\nUse \"tsuru job info NiltonSantos\" to check the status of the job\n" trans := cmdtest.ConditionalTransport{ Transport: cmdtest.Transport{Message: `{"jobName":"loucoAbreu","status":"success"}`, Status: http.StatusCreated}, CondFunc: func(r *http.Request) bool { @@ -715,7 +720,7 @@ func (s *S) TestJobUpdate(c *check.C) { Stdout: &stdout, Stderr: &stderr, } - expected := "Job updated\nUse \"tsuru job info tulioMaravilha\" to check the status of the job\n" + expected := "Job update with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\nJob updated\nUse \"tsuru job info tulioMaravilha\" to check the status of the job\n" trans := cmdtest.ConditionalTransport{ Transport: cmdtest.Transport{Message: "", Status: http.StatusOK}, CondFunc: func(r *http.Request) bool { @@ -756,7 +761,7 @@ func (s *S) TestJobUpdateJSONCommands(c *check.C) { Stdout: &stdout, Stderr: &stderr, } - expected := "Job updated\nUse \"tsuru job info tulioMaravilha\" to check the status of the job\n" + expected := "Job update with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\nJob updated\nUse \"tsuru job info tulioMaravilha\" to check the status of the job\n" trans := cmdtest.ConditionalTransport{ Transport: cmdtest.Transport{Message: "", Status: http.StatusOK}, CondFunc: func(r *http.Request) bool { diff --git a/tsuru/client/testdata/deploy5/Dockerfile b/tsuru/client/testdata/deploy5/Dockerfile new file mode 100644 index 000000000..19eb2a4bc --- /dev/null +++ b/tsuru/client/testdata/deploy5/Dockerfile @@ -0,0 +1,3 @@ +FROM busybox:1.36.0-glibc + +COPY ./job.sh /usr/local/bin/ diff --git a/tsuru/client/testdata/deploy5/job.sh b/tsuru/client/testdata/deploy5/job.sh new file mode 100644 index 000000000..eac431717 --- /dev/null +++ b/tsuru/client/testdata/deploy5/job.sh @@ -0,0 +1 @@ +echo "My job here is done!" diff --git a/tsuru/main.go b/tsuru/main.go index 6e2f6fa6c..3b3857076 100644 --- a/tsuru/main.go +++ b/tsuru/main.go @@ -165,6 +165,7 @@ Services aren’t managed by tsuru, but by their creators.`) m.Register(&client.JobDelete{}) m.Register(&client.JobTrigger{}) m.Register(&client.JobLog{}) + m.Register(&client.JobDeploy{}) m.Register(&client.PluginInstall{}) m.Register(&client.PluginRemove{})