Skip to content

Commit 7c800a9

Browse files
0x2b3bfa0restyled-commitsDavidGOrtega
authored
Provide a standalone command-line tool (#610)
* Draft command-line tool support Try it by running `make && ./terraform-provider-iterative --help` * Add list command and underlying methods * Polish rough edges and allow script on task.yaml * Sanitize identifier generation * Improve code structure * Use main.tf as the configuration format * Restyled by gofmt * Restyled by gofmt * Improve name experience * Allow deterministic names and improve viper errors * Print created task identifier to standard output * Bind stop command 🤦 * Fix log prefixes * Run go mod tidy * Apply suggestions from code review Co-authored-by: Restyled.io <commits@restyled.io> Co-authored-by: DavidGOrtega <g.ortega.david@gmail.com>
1 parent 86d4d75 commit 7c800a9

File tree

25 files changed

+918
-99
lines changed

25 files changed

+918
-99
lines changed

cmd/create/create.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package create
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
"time"
8+
9+
"github.com/alessio/shellescape"
10+
"github.com/sirupsen/logrus"
11+
"github.com/spf13/cobra"
12+
13+
"terraform-provider-iterative/task"
14+
"terraform-provider-iterative/task/common"
15+
)
16+
17+
type Options struct {
18+
Environment map[string]string
19+
Image string
20+
Machine string
21+
Name string
22+
Output string
23+
Parallelism int
24+
PermissionSet string
25+
Script string
26+
Spot bool
27+
Storage int
28+
Tags map[string]string
29+
Timeout int
30+
Workdir string
31+
}
32+
33+
func New(cloud *common.Cloud) *cobra.Command {
34+
o := Options{}
35+
36+
cmd := &cobra.Command{
37+
Use: "create [command...]",
38+
Short: "Create a task",
39+
Long: ``,
40+
RunE: func(cmd *cobra.Command, args []string) error {
41+
return o.Run(cmd, args, cloud)
42+
},
43+
}
44+
45+
cmd.Flags().StringToStringVar(&o.Environment, "environment", map[string]string{}, "environment variables")
46+
cmd.Flags().StringVar(&o.Image, "image", "ubuntu", "machine image")
47+
cmd.Flags().StringVar(&o.Machine, "machine", "m", "machine type")
48+
cmd.Flags().StringVar(&o.Name, "name", "", "deterministic name")
49+
cmd.Flags().StringVar(&o.Output, "output", "", "output directory to download")
50+
cmd.Flags().IntVar(&o.Parallelism, "parallelism", 1, "parallelism")
51+
cmd.Flags().StringVar(&o.PermissionSet, "permission-set", "", "permission set")
52+
cmd.Flags().StringVar(&o.Script, "script", "", "script to run")
53+
cmd.Flags().BoolVar(&o.Spot, "spot", false, "use spot instances")
54+
cmd.Flags().IntVar(&o.Storage, "disk-size", -1, "disk size in gigabytes")
55+
cmd.Flags().StringToStringVar(&o.Tags, "tags", map[string]string{}, "resource tags")
56+
cmd.Flags().IntVar(&o.Timeout, "timeout", 24*60*60, "timeout")
57+
cmd.Flags().StringVar(&o.Workdir, "workdir", ".", "working directory to upload")
58+
59+
return cmd
60+
}
61+
62+
func (o *Options) Run(cmd *cobra.Command, args []string, cloud *common.Cloud) error {
63+
variables := make(map[string]*string)
64+
for name, value := range o.Environment {
65+
variables[name] = nil
66+
if value != "" {
67+
variables[name] = &value
68+
}
69+
}
70+
71+
script := o.Script
72+
if !strings.HasPrefix(script, "#!") {
73+
script = "#!/bin/sh\n" + script
74+
}
75+
script += "\n" + shellescape.QuoteCommand(args)
76+
77+
cfg := common.Task{
78+
Size: common.Size{
79+
Machine: o.Machine,
80+
Storage: o.Storage,
81+
},
82+
Environment: common.Environment{
83+
Image: o.Image,
84+
Script: script,
85+
Variables: variables,
86+
Directory: o.Workdir,
87+
DirectoryOut: o.Output,
88+
Timeout: time.Duration(o.Timeout) * time.Second,
89+
},
90+
Firewall: common.Firewall{
91+
Ingress: common.FirewallRule{
92+
Ports: &[]uint16{22},
93+
},
94+
},
95+
Parallelism: uint16(1),
96+
}
97+
98+
cfg.Spot = common.Spot(common.SpotDisabled)
99+
if o.Spot {
100+
cfg.Spot = common.Spot(common.SpotEnabled)
101+
}
102+
103+
id := common.NewRandomIdentifier()
104+
105+
if o.Name != "" {
106+
id = common.NewIdentifier(o.Name)
107+
if identifier, err := common.ParseIdentifier(o.Name); err == nil {
108+
id = identifier
109+
}
110+
}
111+
112+
ctx, cancel := context.WithTimeout(context.Background(), cloud.Timeouts.Create)
113+
defer cancel()
114+
115+
tsk, err := task.New(ctx, *cloud, id, cfg)
116+
if err != nil {
117+
return err
118+
}
119+
120+
if err := tsk.Create(ctx); err != nil {
121+
logrus.Errorf("Failed to create a new task: %v", err)
122+
logrus.Warn("Attempting to delete residual resources...")
123+
if err := tsk.Delete(ctx); err != nil {
124+
logrus.Errorf("Failed to delete residual resources")
125+
return err
126+
}
127+
return err
128+
}
129+
130+
fmt.Println(id.Long())
131+
return nil
132+
}

cmd/delete/delete.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package delete
2+
3+
import (
4+
"context"
5+
6+
"github.com/spf13/cobra"
7+
8+
"terraform-provider-iterative/task"
9+
"terraform-provider-iterative/task/common"
10+
)
11+
12+
type Options struct {
13+
Workdir string
14+
Output string
15+
}
16+
17+
func New(cloud *common.Cloud) *cobra.Command {
18+
o := Options{}
19+
20+
cmd := &cobra.Command{
21+
Use: "delete <name>",
22+
Short: "Delete a task",
23+
Long: ``,
24+
Args: cobra.ExactArgs(1),
25+
RunE: func(cmd *cobra.Command, args []string) error {
26+
return o.Run(cmd, args, cloud)
27+
},
28+
}
29+
30+
cmd.Flags().StringVar(&o.Output, "output", "", "output directory, relative to workdir")
31+
cmd.Flags().StringVar(&o.Workdir, "workdir", ".", "working directory")
32+
33+
return cmd
34+
}
35+
36+
func (o *Options) Run(cmd *cobra.Command, args []string, cloud *common.Cloud) error {
37+
cfg := common.Task{
38+
Environment: common.Environment{
39+
Directory: o.Workdir,
40+
DirectoryOut: o.Output,
41+
},
42+
}
43+
44+
ctx, cancel := context.WithTimeout(context.Background(), cloud.Timeouts.Delete)
45+
defer cancel()
46+
47+
id, err := common.ParseIdentifier(args[0])
48+
if err != nil {
49+
return err
50+
}
51+
52+
tsk, err := task.New(ctx, *cloud, id, cfg)
53+
if err != nil {
54+
return err
55+
}
56+
57+
return tsk.Delete(ctx)
58+
}

cmd/list/list.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package list
2+
3+
import (
4+
"context"
5+
6+
"github.com/sirupsen/logrus"
7+
"github.com/spf13/cobra"
8+
9+
"terraform-provider-iterative/task"
10+
"terraform-provider-iterative/task/common"
11+
)
12+
13+
type Options struct {
14+
}
15+
16+
func New(cloud *common.Cloud) *cobra.Command {
17+
o := Options{}
18+
19+
cmd := &cobra.Command{
20+
Use: "list",
21+
Short: "List tasks",
22+
Long: ``,
23+
RunE: func(cmd *cobra.Command, args []string) error {
24+
return o.Run(cmd, args, cloud)
25+
},
26+
}
27+
28+
return cmd
29+
}
30+
31+
func (o *Options) Run(cmd *cobra.Command, args []string, cloud *common.Cloud) error {
32+
ctx, cancel := context.WithTimeout(context.Background(), cloud.Timeouts.Read)
33+
defer cancel()
34+
35+
lst, err := task.List(ctx, *cloud)
36+
if err != nil {
37+
return err
38+
}
39+
40+
for _, id := range lst {
41+
logrus.Info(id.Long())
42+
}
43+
44+
return nil
45+
}

cmd/read/read.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package read
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/sirupsen/logrus"
9+
"github.com/spf13/cobra"
10+
11+
"terraform-provider-iterative/task"
12+
"terraform-provider-iterative/task/common"
13+
)
14+
15+
type Options struct {
16+
Parallelism int
17+
}
18+
19+
func New(cloud *common.Cloud) *cobra.Command {
20+
o := Options{}
21+
22+
cmd := &cobra.Command{
23+
Use: "read <name>",
24+
Short: "Read the status of a task",
25+
Long: ``,
26+
Args: cobra.ExactArgs(1),
27+
RunE: func(cmd *cobra.Command, args []string) error {
28+
return o.Run(cmd, args, cloud)
29+
},
30+
}
31+
32+
cmd.Flags().IntVar(&o.Parallelism, "parallelism", 1, "parallelism")
33+
34+
return cmd
35+
}
36+
37+
func (o *Options) Run(cmd *cobra.Command, args []string, cloud *common.Cloud) error {
38+
cfg := common.Task{
39+
Environment: common.Environment{
40+
Image: "ubuntu",
41+
},
42+
}
43+
44+
ctx, cancel := context.WithTimeout(context.Background(), cloud.Timeouts.Read)
45+
defer cancel()
46+
47+
id, err := common.ParseIdentifier(args[0])
48+
if err != nil {
49+
return err
50+
}
51+
52+
tsk, err := task.New(ctx, *cloud, id, cfg)
53+
if err != nil {
54+
return err
55+
}
56+
57+
if err := tsk.Read(ctx); err != nil {
58+
return err
59+
}
60+
61+
var events []string
62+
for _, event := range tsk.Events(ctx) {
63+
events = append(events, fmt.Sprintf(
64+
"%s: %s\n%s",
65+
event.Time.Format("2006-01-02 15:04:05"),
66+
event.Code,
67+
strings.Join(event.Description, "\n"),
68+
))
69+
}
70+
logrus.Info(events)
71+
72+
logs, err := tsk.Logs(ctx)
73+
if err != nil {
74+
return err
75+
}
76+
77+
for index, log := range logs {
78+
for _, line := range strings.Split(strings.Trim(log, "\n"), "\n") {
79+
logrus.Infof("\x1b[%dmLOG %d >> %s", 35, index, line)
80+
}
81+
}
82+
83+
status, err := tsk.Status(ctx)
84+
if err != nil {
85+
return err
86+
}
87+
88+
message := fmt.Sprintf("\x1b[%dmStatus: queued \x1b[1m•\x1b[0m", 34)
89+
90+
if status["succeeded"] >= o.Parallelism {
91+
message = fmt.Sprintf("\x1b[%dmStatus: completed successfully \x1b[1m•\x1b[0m", 32)
92+
}
93+
if status["failed"] > 0 {
94+
message = fmt.Sprintf("\x1b[%dmStatus: completed with errors \x1b[1m•\x1b[0m", 31)
95+
}
96+
if status["running"] >= o.Parallelism {
97+
message = fmt.Sprintf("\x1b[%dmStatus: running \x1b[1m•\x1b[0m", 33)
98+
}
99+
100+
logrus.Info(message)
101+
return nil
102+
}

0 commit comments

Comments
 (0)