Skip to content

Commit 7a9f6bc

Browse files
chore: fix file namings
1 parent 0454ec2 commit 7a9f6bc

33 files changed

Lines changed: 183 additions & 9 deletions

README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,22 @@ createos --help
127127
| -------------------------------- | -------------------------------------- |
128128
| `createos cronjobs list` | List cron jobs for a project |
129129
| `createos cronjobs create` | Create a new HTTP cron job |
130-
| `createos cronjobs get` | Show details for a cron job |
131-
| `createos cronjobs update` | Update a cron job's settings |
130+
| `createos cronjobs get` | Show details for a cron job (including path, method, headers, body) |
131+
| `createos cronjobs update` | Update a cron job's name, schedule, or HTTP settings |
132132
| `createos cronjobs suspend` | Pause a cron job |
133133
| `createos cronjobs unsuspend` | Resume a suspended cron job |
134134
| `createos cronjobs activities` | Show recent execution history |
135135
| `createos cronjobs delete` | Delete a cron job |
136136

137+
**HTTP settings flags** (for `create` and `update`):
138+
139+
| Flag | Description |
140+
| ---- | ----------- |
141+
| `--path` | HTTP path to call, must start with `/` |
142+
| `--method` | HTTP method: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD` |
143+
| `--header` | Header in `Key=Value` format, repeatable |
144+
| `--body` | JSON body to send with the request (only for POST, PUT, PATCH) |
145+
137146
### Templates
138147

139148
| Command | Description |
@@ -230,9 +239,25 @@ createos domains delete --project <id> --domain <id> --force
230239

231240
# Cron jobs
232241
createos cronjobs list --project <id>
242+
243+
# Simple GET cron job
233244
createos cronjobs create --project <id> --environment <id> \
234245
--name "Cleanup job" --schedule "0 * * * *" \
235-
--path /api/cleanup --method POST
246+
--path /api/cleanup --method GET
247+
248+
# POST cron job with headers and JSON body
249+
createos cronjobs create --project <id> --environment <id> \
250+
--name "Webhook" --schedule "*/5 * * * *" \
251+
--path /api/hook --method POST \
252+
--header "Authorization=Bearer token" --header "X-Source=cron" \
253+
--body '{"event":"tick"}'
254+
255+
# Update HTTP settings (headers and body preserved if omitted)
256+
createos cronjobs update --project <id> --cronjob <id> \
257+
--path /api/hook --method POST \
258+
--header "Authorization=Bearer token" --body '{"event":"tick"}'
259+
260+
createos cronjobs get --project <id> --cronjob <id>
236261
createos cronjobs delete --project <id> --cronjob <id> --force
237262

238263
# Templates
Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cronjobs
33
import (
44
"encoding/json"
55
"fmt"
6+
"strings"
67

78
"github.com/pterm/pterm"
89
"github.com/urfave/cli/v2"
@@ -21,14 +22,23 @@ func newCronjobsCreateCommand() *cli.Command {
2122
Examples:
2223
createos cronjobs create --project <project-id> --environment <env-id> \
2324
--name "Cleanup job" --schedule "0 * * * *" \
24-
--path /api/cleanup --method POST`,
25+
--path /api/cleanup --method POST
26+
27+
# With custom headers and JSON body:
28+
createos cronjobs create --project <project-id> --environment <env-id> \
29+
--name "Webhook" --schedule "*/5 * * * *" \
30+
--path /api/hook --method POST \
31+
--header "Authorization=Bearer token" --header "X-Source=cron" \
32+
--body '{"event":"tick"}'`,
2533
Flags: []cli.Flag{
2634
&cli.StringFlag{Name: "project", Usage: "Project ID"},
2735
&cli.StringFlag{Name: "environment", Usage: "Environment ID to attach the cron job to"},
2836
&cli.StringFlag{Name: "name", Usage: "Name for the cron job"},
2937
&cli.StringFlag{Name: "schedule", Usage: "Cron schedule expression (e.g. \"0 * * * *\")"},
3038
&cli.StringFlag{Name: "path", Usage: "HTTP path to call (must start with /)"},
3139
&cli.StringFlag{Name: "method", Usage: "HTTP method: GET, POST, PUT, DELETE, PATCH, HEAD", Value: "GET"},
40+
&cli.StringSliceFlag{Name: "header", Usage: "HTTP header to send with each request (format: Key=Value, repeatable)"},
41+
&cli.StringFlag{Name: "body", Usage: "JSON body to send with the request"},
3242
},
3343
Action: func(c *cli.Context) error {
3444
client, ok := c.App.Metadata[api.ClientKey].(*api.APIClient)
@@ -46,6 +56,8 @@ Examples:
4656
environmentID := c.String("environment")
4757
path := c.String("path")
4858
method := c.String("method")
59+
headers := parseHeaders(c.StringSlice("header"))
60+
bodyStr := c.String("body")
4961

5062
if !terminal.IsInteractive() {
5163
// Non-TTY: all values must come from flags.
@@ -130,11 +142,48 @@ Examples:
130142
}
131143
method = selected
132144
}
145+
if headers == nil {
146+
headers = map[string]string{}
147+
for {
148+
pair, inputErr := pterm.DefaultInteractiveTextInput.
149+
WithDefaultText("Add header (Key=Value, leave blank to skip)").
150+
Show()
151+
if inputErr != nil {
152+
return fmt.Errorf("could not read header: %w", inputErr)
153+
}
154+
if pair == "" {
155+
break
156+
}
157+
k, v, _ := strings.Cut(pair, "=")
158+
if k != "" {
159+
headers[k] = v
160+
}
161+
}
162+
if len(headers) == 0 {
163+
headers = nil
164+
}
165+
}
166+
if bodyStr == "" && methodSupportsBody(method) {
167+
bodyStr, err = pterm.DefaultInteractiveTextInput.
168+
WithDefaultText("JSON body (leave blank to skip)").
169+
Show()
170+
if err != nil {
171+
return fmt.Errorf("could not read body: %w", err)
172+
}
173+
}
133174
}
134175

135176
settings := api.HTTPCronjobSettings{
136-
Path: path,
137-
Method: method,
177+
Path: path,
178+
Method: method,
179+
Headers: headers,
180+
}
181+
if bodyStr != "" {
182+
if !json.Valid([]byte(bodyStr)) {
183+
return fmt.Errorf("body must be valid JSON (e.g. '{\"key\":\"value\"}')")
184+
}
185+
raw := json.RawMessage(bodyStr)
186+
settings.Body = &raw
138187
}
139188
settingsJSON, err := json.Marshal(settings)
140189
if err != nil {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,30 @@ func newCronjobsGetCommand() *cli.Command {
6868
fmt.Println(*cj.SuspendText)
6969
}
7070

71+
if cj.Settings != nil {
72+
var s api.HTTPCronjobSettings
73+
if err := json.Unmarshal(*cj.Settings, &s); err == nil {
74+
label.Print("Path: ")
75+
fmt.Println(s.Path)
76+
label.Print("Method: ")
77+
fmt.Println(s.Method)
78+
if len(s.Headers) > 0 {
79+
label.Println("Headers:")
80+
for k, v := range s.Headers {
81+
fmt.Printf(" %s=%s\n", k, v)
82+
}
83+
}
84+
if s.Body != nil {
85+
label.Print("Body: ")
86+
fmt.Println(string(*s.Body))
87+
}
88+
if s.TimeoutInSeconds != nil {
89+
label.Print("Timeout (s): ")
90+
fmt.Println(*s.TimeoutInSeconds)
91+
}
92+
}
93+
}
94+
7195
label.Print("Created At: ")
7296
fmt.Println(cj.CreatedAt.Format("2006-01-02 15:04:05"))
7397
label.Print("Updated At: ")

cmd/cronjobs/helpers.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cronjobs
22

33
import (
44
"fmt"
5+
"strings"
56

67
"github.com/pterm/pterm"
78
"github.com/urfave/cli/v2"
@@ -11,6 +12,30 @@ import (
1112
"github.com/NodeOps-app/createos-cli/internal/terminal"
1213
)
1314

15+
// methodSupportsBody returns true for HTTP methods that typically carry a request body.
16+
func methodSupportsBody(method string) bool {
17+
switch strings.ToUpper(method) {
18+
case "POST", "PUT", "PATCH":
19+
return true
20+
}
21+
return false
22+
}
23+
24+
// parseHeaders converts a slice of "Key=Value" strings into a map.
25+
func parseHeaders(pairs []string) map[string]string {
26+
if len(pairs) == 0 {
27+
return nil
28+
}
29+
headers := make(map[string]string, len(pairs))
30+
for _, pair := range pairs {
31+
k, v, _ := strings.Cut(pair, "=")
32+
if k != "" {
33+
headers[k] = v
34+
}
35+
}
36+
return headers
37+
}
38+
1439
// resolveCronjob resolves a project ID and cron job ID from flags or interactively (TTY only).
1540
func resolveCronjob(c *cli.Context, client *api.APIClient) (string, string, error) {
1641
projectID, err := cmdutil.ResolveProjectID(c.String("project"))
Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cronjobs
33
import (
44
"encoding/json"
55
"fmt"
6+
"strings"
67

78
"github.com/pterm/pterm"
89
"github.com/urfave/cli/v2"
@@ -19,14 +20,20 @@ func newCronjobsUpdateCommand() *cli.Command {
1920
2021
Examples:
2122
createos cronjobs update --cronjob <id> --name "New name" --schedule "*/5 * * * *" \
22-
--path /api/ping --method GET`,
23+
--path /api/ping --method GET
24+
25+
# With custom headers and JSON body:
26+
createos cronjobs update --cronjob <id> --path /api/hook --method POST \
27+
--header "Authorization=Bearer token" --body '{"event":"tick"}'`,
2328
Flags: []cli.Flag{
2429
&cli.StringFlag{Name: "project", Usage: "Project ID"},
2530
&cli.StringFlag{Name: "cronjob", Usage: "Cron job ID"},
2631
&cli.StringFlag{Name: "name", Usage: "New name for the cron job"},
2732
&cli.StringFlag{Name: "schedule", Usage: "New cron schedule expression"},
2833
&cli.StringFlag{Name: "path", Usage: "HTTP path to call (must start with /)"},
2934
&cli.StringFlag{Name: "method", Usage: "HTTP method: GET, POST, PUT, DELETE, PATCH, HEAD"},
35+
&cli.StringSliceFlag{Name: "header", Usage: "HTTP header to send with each request (format: Key=Value, repeatable)"},
36+
&cli.StringFlag{Name: "body", Usage: "JSON body to send with the request"},
3037
},
3138
Action: func(c *cli.Context) error {
3239
client, ok := c.App.Metadata[api.ClientKey].(*api.APIClient)
@@ -49,6 +56,8 @@ Examples:
4956
schedule := c.String("schedule")
5057
path := c.String("path")
5158
method := c.String("method")
59+
headers := parseHeaders(c.StringSlice("header"))
60+
bodyStr := c.String("body")
5261

5362
// Decode existing settings for defaults in both TTY and non-TTY.
5463
var currentSettings api.HTTPCronjobSettings
@@ -70,6 +79,9 @@ Examples:
7079
if method == "" {
7180
method = currentSettings.Method
7281
}
82+
if headers == nil {
83+
headers = currentSettings.Headers
84+
}
7385
} else {
7486
if name == "" {
7587
name, err = pterm.DefaultInteractiveTextInput.
@@ -118,11 +130,49 @@ Examples:
118130
}
119131
method = selected
120132
}
133+
if headers == nil {
134+
headers = map[string]string{}
135+
for {
136+
pair, inputErr := pterm.DefaultInteractiveTextInput.
137+
WithDefaultText("Add header (Key=Value, leave blank to skip)").
138+
Show()
139+
if inputErr != nil {
140+
return fmt.Errorf("could not read header: %w", inputErr)
141+
}
142+
if pair == "" {
143+
break
144+
}
145+
k, v, _ := strings.Cut(pair, "=")
146+
if k != "" {
147+
headers[k] = v
148+
}
149+
}
150+
if len(headers) == 0 {
151+
headers = currentSettings.Headers
152+
}
153+
}
154+
if bodyStr == "" && methodSupportsBody(method) {
155+
bodyStr, err = pterm.DefaultInteractiveTextInput.
156+
WithDefaultText("JSON body (leave blank to keep existing)").
157+
Show()
158+
if err != nil {
159+
return fmt.Errorf("could not read body: %w", err)
160+
}
161+
}
121162
}
122163

123164
settings := api.HTTPCronjobSettings{
124-
Path: path,
125-
Method: method,
165+
Path: path,
166+
Method: method,
167+
Headers: headers,
168+
Body: currentSettings.Body,
169+
}
170+
if bodyStr != "" {
171+
if !json.Valid([]byte(bodyStr)) {
172+
return fmt.Errorf("body must be valid JSON (e.g. '{\"key\":\"value\"}')")
173+
}
174+
raw := json.RawMessage(bodyStr)
175+
settings.Body = &raw
126176
}
127177
settingsJSON, err := json.Marshal(settings)
128178
if err != nil {

0 commit comments

Comments
 (0)