Skip to content

Commit 1dd0ad4

Browse files
authored
feat(job): support manual jobs when schedule is empty (#75)
Add support for manual Tsuru jobs by setting the `manual` flag when the `schedule` field is omitted. Update documentation and schema to clarify usage. Add acceptance tests to verify manual job creation and behavior. Cleanup test helpers and improve code formatting.
1 parent aacffd4 commit 1dd0ad4

File tree

4 files changed

+140
-33
lines changed

4 files changed

+140
-33
lines changed

docs/resources/job.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ resource "tsuru_job" "full-featured-job" {
6666
- `container` (Block List, Max: 1) (see [below for nested schema](#nestedblock--container))
6767
- `description` (String) Job description
6868
- `metadata` (Block List, Max: 1) (see [below for nested schema](#nestedblock--metadata))
69-
- `schedule` (String) Cron-like schedule for when the job should be triggered
69+
- `schedule` (String) Cron-like schedule for when the job should be triggered (keep empty for manual jobs)
7070
- `tags` (List of String) Tags
7171
- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
7272

internal/provider/resource_tsuru_job.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func resourceTsuruJob() *schema.Resource {
9292

9393
"schedule": {
9494
Type: schema.TypeString,
95-
Description: "Cron-like schedule for when the job should be triggered",
95+
Description: "Cron-like schedule for when the job should be triggered (keep empty for manual jobs)",
9696
Optional: true,
9797
},
9898

@@ -155,7 +155,6 @@ func resourceTsuruJobCreate(ctx context.Context, d *schema.ResourceData, meta in
155155
}
156156
return nil
157157
})
158-
159158
if err != nil {
160159
return diag.Errorf("unable to create job %s: %v", job.Name, err)
161160
}
@@ -207,7 +206,6 @@ func resourceTsuruJobUpdate(ctx context.Context, d *schema.ResourceData, meta in
207206

208207
return nil
209208
})
210-
211209
if err != nil {
212210
return diag.Errorf("unable to update job %s: %v", jobName, err)
213211
}
@@ -268,7 +266,6 @@ func resourceTsuruJobDelete(ctx context.Context, d *schema.ResourceData, meta in
268266
}
269267
return nil
270268
})
271-
272269
if err != nil {
273270
return diag.Errorf("unable to delete job %s: %v", name, err)
274271
}
@@ -334,6 +331,8 @@ func inputJobFromResourceData(ctx context.Context, d *schema.ResourceData, provi
334331

335332
if schedule, ok := d.GetOk("schedule"); ok {
336333
job.Schedule = schedule.(string)
334+
} else {
335+
job.Manual = true
337336
}
338337
if concurrencyPolicyInterface, ok := d.GetOk("concurrency_policy"); ok {
339338
concurrencyPolicy := concurrencyPolicyInterface.(string)

internal/provider/resource_tsuru_job_test.go

Lines changed: 134 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,126 @@ func TestAccResourceTsuruJobBasic(t *testing.T) {
110110
})
111111
}
112112

113+
func testAccResourceTsuruJob_basic() string {
114+
return `
115+
resource "tsuru_job" "job" {
116+
name = "job01"
117+
description = "my job description"
118+
plan = "c1m1"
119+
team_owner = "my-team"
120+
pool = "prod"
121+
schedule = "* * * * *"
122+
}
123+
`
124+
}
125+
126+
func TestAccResourceTsuruJobManual(t *testing.T) {
127+
fakeServer := echo.New()
128+
129+
iterationCount := 0
130+
131+
fakeServer.GET("/1.0/pools", func(c echo.Context) error {
132+
return c.JSON(http.StatusOK, []tsuru.Pool{{Name: "prod"}})
133+
})
134+
135+
fakeServer.GET("/1.0/plans", func(c echo.Context) error {
136+
return c.JSON(http.StatusOK, []tsuru.Plan{{Name: "c1m1"}})
137+
})
138+
139+
fakeServer.POST("/1.13/jobs", func(c echo.Context) error {
140+
job := tsuru.InputJob{}
141+
c.Bind(&job)
142+
assert.Equal(t, "job01", job.Name)
143+
assert.Equal(t, "my job description", job.Description)
144+
assert.Equal(t, "c1m1", job.Plan)
145+
assert.Equal(t, "my-team", job.TeamOwner)
146+
assert.Equal(t, "prod", job.Pool)
147+
assert.Equal(t, "", job.Schedule)
148+
assert.Equal(t, true, job.Manual)
149+
assert.Nil(t, job.Container.Command)
150+
assert.Equal(t, "", job.Container.Image)
151+
152+
iterationCount++
153+
return c.JSON(http.StatusOK, map[string]interface{}{
154+
"status": "success",
155+
"jobName": job.Name,
156+
})
157+
})
158+
159+
fakeServer.GET("/1.13/jobs/:name", func(c echo.Context) error {
160+
name := c.Param("name")
161+
if name != "job01" {
162+
return nil
163+
}
164+
165+
if iterationCount == 1 {
166+
job := &tsuru.Job{
167+
Name: name,
168+
Description: "my job description",
169+
TeamOwner: "my-team",
170+
Plan: tsuru.Plan{Name: "c1m1"},
171+
Pool: "prod",
172+
Spec: tsuru.JobSpec{
173+
Schedule: "",
174+
Manual: true,
175+
},
176+
}
177+
return c.JSON(http.StatusOK, tsuru.JobInfo{Job: *job})
178+
}
179+
180+
return c.JSON(http.StatusNotFound, nil)
181+
})
182+
183+
fakeServer.PUT("/1.13/jobs/:name", func(c echo.Context) error {
184+
return c.JSON(http.StatusOK, nil)
185+
})
186+
187+
fakeServer.DELETE("/1.13/jobs/:name", func(c echo.Context) error {
188+
name := c.Param("name")
189+
assert.Equal(t, "job01", name)
190+
return c.NoContent(http.StatusNoContent)
191+
})
192+
193+
fakeServer.HTTPErrorHandler = func(err error, c echo.Context) {
194+
t.Errorf("methods=%s, path=%s, err=%s", c.Request().Method, c.Path(), err.Error())
195+
}
196+
server := httptest.NewServer(fakeServer)
197+
os.Setenv("TSURU_TARGET", server.URL)
198+
199+
resourceName := "tsuru_job.job"
200+
resource.Test(t, resource.TestCase{
201+
PreCheck: func() { testAccPreCheck(t) },
202+
ProviderFactories: testAccProviderFactories,
203+
CheckDestroy: nil,
204+
Steps: []resource.TestStep{
205+
{
206+
Config: testAccResourceTsuruJob_manual(),
207+
Check: resource.ComposeAggregateTestCheckFunc(
208+
testAccResourceExists(resourceName),
209+
resource.TestCheckResourceAttr(resourceName, "name", "job01"),
210+
resource.TestCheckResourceAttr(resourceName, "description", "my job description"),
211+
resource.TestCheckResourceAttr(resourceName, "plan", "c1m1"),
212+
resource.TestCheckResourceAttr(resourceName, "team_owner", "my-team"),
213+
resource.TestCheckResourceAttr(resourceName, "pool", "prod"),
214+
resource.TestCheckResourceAttr(resourceName, "schedule", ""),
215+
),
216+
},
217+
},
218+
})
219+
}
220+
221+
func testAccResourceTsuruJob_manual() string {
222+
return `
223+
resource "tsuru_job" "job" {
224+
name = "job01"
225+
description = "my job description"
226+
plan = "c1m1"
227+
team_owner = "my-team"
228+
pool = "prod"
229+
}
230+
`
231+
}
232+
113233
func TestAccResourceTsuruJobComplete(t *testing.T) {
114234
fakeServer := echo.New()
115235

@@ -220,15 +340,23 @@ func TestAccResourceTsuruJobComplete(t *testing.T) {
220340
})
221341
}
222342

223-
func testAccResourceTsuruJob_basic() string {
343+
func testAccResourceTsuruJob_complete() string {
224344
return `
225345
resource "tsuru_job" "job" {
226-
name = "job01"
346+
name = "job01"
227347
description = "my job description"
228-
plan = "c1m1"
229-
team_owner = "my-team"
230-
pool = "prod"
231-
schedule = "* * * * *"
348+
plan = "c1m1"
349+
team_owner = "my-team"
350+
pool = "prod"
351+
schedule = "* * * * *"
352+
container {
353+
image = "tsuru/scratch:latest"
354+
command = ["sleep", 600]
355+
}
356+
357+
358+
active_deadline_seconds = 300
359+
concurrency_policy = "Forbid"
232360
}
233361
`
234362
}
@@ -407,27 +535,6 @@ func TestAccResourceTsuruJobMetadataDeletion(t *testing.T) {
407535
})
408536
}
409537

410-
func testAccResourceTsuruJob_complete() string {
411-
return `
412-
resource "tsuru_job" "job" {
413-
name = "job01"
414-
description = "my job description"
415-
plan = "c1m1"
416-
team_owner = "my-team"
417-
pool = "prod"
418-
schedule = "* * * * *"
419-
container {
420-
image = "tsuru/scratch:latest"
421-
command = ["sleep", 600]
422-
}
423-
424-
425-
active_deadline_seconds = 300
426-
concurrency_policy = "Forbid"
427-
}
428-
`
429-
}
430-
431538
func testAccResourceTsuruJob_metadata() string {
432539
return `
433540
resource "tsuru_job" "job" {

main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ import (
1212

1313
func main() {
1414
plugin.Serve(&plugin.ServeOpts{
15-
ProviderFunc: provider.Provider})
15+
ProviderFunc: provider.Provider,
16+
})
1617
}

0 commit comments

Comments
 (0)