From 6981641006de7a3bf82f9c7ad2b71958cdfabfca Mon Sep 17 00:00:00 2001 From: crgisch Date: Mon, 9 Sep 2024 12:51:14 -0300 Subject: [PATCH 1/5] feat: job with dockerfile support --- tsuru/client/build.go | 10 +-- tsuru/client/deploy.go | 200 +++++++++++++++++++++++++++++++++++++++++ tsuru/client/jobs.go | 20 +++-- tsuru/main.go | 1 + 4 files changed, 220 insertions(+), 11 deletions(-) diff --git a/tsuru/client/build.go b/tsuru/client/build.go index 4a3d9c48..ac399843 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 0e4d6597..d4679ff2 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,202 @@ 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 + imageCommand 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 the application 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 app's platform build process (just sending source code and/or configurations): + Uploading all files within the current directory + $ tsuru app deploy -a . + + Uploading all files within a specific directory + $ tsuru app deploy -a mysite/ + + Uploading specific files + $ tsuru app deploy -a ./myfile.jar ./Procfile + + Uploading specific files (ignoring their base directories) + $ tsuru app deploy -a --files-only ./my-code/main.go ./tsuru_stuff/Procfile + + To deploy using a container image: + $ tsuru app deploy -a --image registry.example.com/my-company/app: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 app deploy -a --dockerfile . + + Sending a specific container file and specific directory as container build context: + $ tsuru app deploy -a --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/jobs.go b/tsuru/client/jobs.go index e278ad27..f4eb0dab 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) { diff --git a/tsuru/main.go b/tsuru/main.go index 2463cdd3..5430ded4 100644 --- a/tsuru/main.go +++ b/tsuru/main.go @@ -164,6 +164,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{}) From 0d35870925f399764c586aa42fecaeaf0a1c1e07 Mon Sep 17 00:00:00 2001 From: crgisch Date: Mon, 9 Sep 2024 12:51:39 -0300 Subject: [PATCH 2/5] test: job with dockerfile support --- tsuru/client/deploy_test.go | 276 +++++++++++++++++++++++ tsuru/client/jobs_test.go | 10 +- tsuru/client/testdata/deploy5/Dockerfile | 3 + tsuru/client/testdata/deploy5/job.sh | 1 + 4 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 tsuru/client/testdata/deploy5/Dockerfile create mode 100644 tsuru/client/testdata/deploy5/job.sh diff --git a/tsuru/client/deploy_test.go b/tsuru/client/deploy_test.go index fe8e15cb..f098b17c 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_test.go b/tsuru/client/jobs_test.go index 819be655..aaab8c03 100644 --- a/tsuru/client/jobs_test.go +++ b/tsuru/client/jobs_test.go @@ -25,7 +25,7 @@ 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\nJob 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 +66,7 @@ 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\nJob 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 +105,7 @@ 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\nJob 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 +147,7 @@ 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\nJob 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 +176,7 @@ 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\nJob 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 { diff --git a/tsuru/client/testdata/deploy5/Dockerfile b/tsuru/client/testdata/deploy5/Dockerfile new file mode 100644 index 00000000..19eb2a4b --- /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 00000000..eac43171 --- /dev/null +++ b/tsuru/client/testdata/deploy5/job.sh @@ -0,0 +1 @@ +echo "My job here is done!" From 10981a90261a8c51b8e52432b00069d5053a0091 Mon Sep 17 00:00:00 2001 From: crgisch Date: Mon, 9 Sep 2024 13:04:47 -0300 Subject: [PATCH 3/5] lint: remove unused variable --- tsuru/client/deploy.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tsuru/client/deploy.go b/tsuru/client/deploy.go index d4679ff2..cbfc21f1 100644 --- a/tsuru/client/deploy.go +++ b/tsuru/client/deploy.go @@ -578,14 +578,13 @@ func (c *deployVersionArgs) values(values url.Values) { var _ cmd.Cancelable = &JobDeploy{} type JobDeploy struct { - jobName string - image string - imageCommand string - message string - dockerfile string - eventID string - fs *gnuflag.FlagSet - m sync.Mutex + jobName string + image string + message string + dockerfile string + eventID string + fs *gnuflag.FlagSet + m sync.Mutex } func (c *JobDeploy) Flags() *gnuflag.FlagSet { From b6a08a22180f0d8e54830ef1cc685f451d8b983b Mon Sep 17 00:00:00 2001 From: crgisch Date: Tue, 10 Sep 2024 14:18:18 -0300 Subject: [PATCH 4/5] fix: job deploy exemples --- tsuru/client/deploy.go | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/tsuru/client/deploy.go b/tsuru/client/deploy.go index cbfc21f1..51ec3ae1 100644 --- a/tsuru/client/deploy.go +++ b/tsuru/client/deploy.go @@ -607,33 +607,20 @@ 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 the application on Tsuru. + 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 app's platform build process (just sending source code and/or configurations): - Uploading all files within the current directory - $ tsuru app deploy -a . - - Uploading all files within a specific directory - $ tsuru app deploy -a mysite/ - - Uploading specific files - $ tsuru app deploy -a ./myfile.jar ./Procfile - - Uploading specific files (ignoring their base directories) - $ tsuru app deploy -a --files-only ./my-code/main.go ./tsuru_stuff/Procfile - To deploy using a container image: - $ tsuru app deploy -a --image registry.example.com/my-company/app:v42 + $ 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 app deploy -a --dockerfile . + $ tsuru job deploy -j --dockerfile . Sending a specific container file and specific directory as container build context: - $ tsuru app deploy -a --dockerfile ./Dockerfile.other ./other/ + $ tsuru job deploy -j --dockerfile ./Dockerfile.other ./other/ `, MinArgs: 0, } From 5d06e497841be73523794a2800801cbfeab1cf9b Mon Sep 17 00:00:00 2001 From: crgisch Date: Fri, 20 Sep 2024 10:53:48 -0300 Subject: [PATCH 5/5] chore: warning for job update with image --- tsuru/client/jobs.go | 3 +++ tsuru/client/jobs_test.go | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/tsuru/client/jobs.go b/tsuru/client/jobs.go index f4eb0dab..2af100a2 100644 --- a/tsuru/client/jobs.go +++ b/tsuru/client/jobs.go @@ -566,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 aaab8c03..7ccd94b3 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 creation with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\nJob 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 creation with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\nJob 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 creation with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\nJob 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 creation with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\nJob 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 creation with image is being deprecated. You should use 'tsuru job deploy' to set a job`s image\nJob 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 {