Skip to content

Commit cbba8d8

Browse files
authored
Merge pull request #44 from crytic/qol-touch-up
Qol touch up
2 parents 1f11b45 + a686048 commit cbba8d8

File tree

21 files changed

+306
-282
lines changed

21 files changed

+306
-282
lines changed

.gitignore

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
*.backup
1+
# Environment
22
.direnv/
33
.env
4+
5+
# Editors
46
.vscode
57
.vscode/
6-
bin/
8+
9+
# Output
710
dist/
8-
example/input.zip
911
result
12+
cloudexec/
13+
example/input/crytic-export/
14+
example/input/output/
15+
example/input.zip

cmd/cloudexec/cancel.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,35 @@ import (
66

77
"github.com/crytic/cloudexec/pkg/config"
88
do "github.com/crytic/cloudexec/pkg/digitalocean"
9+
"github.com/crytic/cloudexec/pkg/log"
910
"github.com/crytic/cloudexec/pkg/state"
1011
)
1112

1213
func CancelJob(config config.Config, existingState *state.State, job *state.Job, force bool) error {
1314
if job.Status != state.Provisioning && job.Status != state.Running {
14-
return fmt.Errorf("Job %v is not running, it is %s", job.ID, job.Status)
15+
log.Info("Job %v is not running, it is %s", job.ID, job.Status)
16+
return nil
1517
}
16-
fmt.Printf("Destroying droplet %s associated with job %v: IP=%v | CreatedAt=%s\n", job.Droplet.Name, job.ID, job.Droplet.IP, job.Droplet.Created)
18+
log.Warn("Destroying droplet %s associated with job %v: IP=%v | CreatedAt=%s", job.Droplet.Name, job.ID, job.Droplet.IP, job.Droplet.Created)
1719
if !force { // Ask for confirmation before cleaning this job if no force flag
18-
fmt.Println("Confirm? (y/n)")
20+
log.Warn("Confirm? (y/n)")
1921
var response string
2022
fmt.Scanln(&response)
2123
if strings.ToLower(response) != "y" {
22-
fmt.Printf("Droplet %s was not destroyed\n", job.Droplet.Name)
24+
log.Info("Droplet %s was not destroyed", job.Droplet.Name)
2325
return nil
2426
}
2527
}
26-
fmt.Printf("Destroying droplet %v...\n", job.Droplet.ID)
2728
err := do.DeleteDroplet(config, job.Droplet.ID)
2829
if err != nil {
2930
return fmt.Errorf("Failed to destroy droplet: %w", err)
3031
}
31-
fmt.Printf("Marking job %v as cancelled...\n", job.Droplet.ID)
32+
log.Good("Droplet %v destroyed", job.Droplet.Name)
3233
err = existingState.CancelRunningJob(config, job.ID)
3334
if err != nil {
34-
return fmt.Errorf("Failed to mark job as cancelled: %w", err)
35+
return fmt.Errorf("Failed to change job status to cancelled: %w", err)
3536
}
37+
log.Good("Job %v status changed to cancelled", job.ID)
3638
return nil
3739
}
3840

@@ -42,17 +44,17 @@ func CancelAll(config config.Config, existingState *state.State, force bool) err
4244
return fmt.Errorf("Failed to get all running servers: %w", err)
4345
}
4446
if len(droplets) == 0 {
45-
fmt.Println("No running servers found")
47+
log.Info("No running servers found")
4648
return nil
4749
}
48-
fmt.Printf("Found %v running server(s):\n", len(droplets))
50+
log.Info("Found %v running server(s):", len(droplets))
4951
for _, job := range existingState.Jobs {
5052
if job.Status != state.Provisioning && job.Status != state.Running {
5153
continue // skip jobs that aren't running
5254
}
5355
err = CancelJob(config, existingState, &job, force)
5456
if err != nil {
55-
fmt.Printf("Failed to cancel job %v", job.ID)
57+
log.Error("Failed to cancel job %v", job.ID)
5658
}
5759
}
5860
return nil

cmd/cloudexec/clean.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import (
55
"strings"
66

77
"github.com/crytic/cloudexec/pkg/config"
8+
"github.com/crytic/cloudexec/pkg/log"
89
"github.com/crytic/cloudexec/pkg/s3"
910
"github.com/crytic/cloudexec/pkg/ssh"
1011
"github.com/crytic/cloudexec/pkg/state"
1112
)
1213

13-
func CleanBucketJob(config config.Config, existingState *state.State, jobID int64, force bool) error {
14+
func CleanJob(config config.Config, existingState *state.State, jobID int64, force bool) error {
1415
prefix := fmt.Sprintf("job-%v", jobID)
1516
objects, err := s3.ListObjects(config, prefix)
1617
if err != nil {
@@ -19,56 +20,56 @@ func CleanBucketJob(config config.Config, existingState *state.State, jobID int6
1920
// Confirm job data deletion
2021
var numToRm int = len(objects)
2122
if numToRm == 0 {
22-
fmt.Printf("Bucket is already empty.\n")
23+
log.Info("Bucket is already empty.")
2324
return nil
2425
}
25-
fmt.Printf("Removing ALL input, output, and logs associated with %s...\n", prefix)
26+
log.Warn("Removing all input, output, logs, and configuration associated with %s", prefix)
2627
if !force { // Ask for confirmation before cleaning this job if no force flag
27-
fmt.Println("Confirm? (y/n)")
28+
log.Warn("Confirm? (y/n)")
2829
var response string
2930
fmt.Scanln(&response)
3031
if strings.ToLower(response) != "y" {
31-
fmt.Printf("Job %v was not cleaned\n", jobID)
32+
log.Info("Job %v was not cleaned", jobID)
3233
return nil
3334
}
3435
}
35-
fmt.Printf("Deleting bucket contents...\n")
36+
log.Wait("Deleting bucket contents")
3637
// Delete all objects in the bucket
3738
for _, object := range objects {
38-
fmt.Println("Deleting object: ", object)
39+
log.Info("Deleting object: %s", object)
3940
err = s3.DeleteObject(config, object)
4041
if err != nil {
4142
return err
4243
}
4344
}
44-
fmt.Printf("Deleted %d objects from bucket, removing job %v from state file..\n", numToRm, jobID)
45+
log.Good("Bucket is clean")
4546
newState := &state.State{}
4647
deleteJob := state.Job{
4748
ID: jobID,
4849
Delete: true,
4950
}
5051
newState.CreateJob(deleteJob)
5152
err = state.MergeAndSave(config, newState)
53+
log.Good("Removed job %v from state file", jobID)
5254
if err != nil {
53-
return fmt.Errorf("Error removing %s from state file: %w\n", prefix, err)
55+
return fmt.Errorf("Error removing %s from state file: %w", prefix, err)
5456
}
55-
fmt.Printf("Removing ssh config for job %v...\n", jobID)
5657
err = ssh.DeleteSSHConfig(jobID)
5758
if err != nil {
5859
return fmt.Errorf("Failed to delete ssh config: %w", err)
5960
}
6061
return nil
6162
}
6263

63-
func CleanBucketAll(config config.Config, existingState *state.State, force bool) error {
64+
func CleanAll(config config.Config, existingState *state.State, force bool) error {
6465
if len(existingState.Jobs) == 0 {
65-
fmt.Println("No jobs are available")
66+
log.Info("No jobs are available")
6667
return nil
6768
}
6869
for _, job := range existingState.Jobs {
69-
err := CleanBucketJob(config, existingState, job.ID, force)
70+
err := CleanJob(config, existingState, job.ID, force)
7071
if err != nil {
71-
return err
72+
log.Error("Failed to clean job %v", job.ID)
7273
}
7374
}
7475
return nil

cmd/cloudexec/configure.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@ import (
99
"os/user"
1010
"strings"
1111

12-
"github.com/crytic/cloudexec/pkg/config"
1312
"golang.org/x/term"
13+
14+
"github.com/crytic/cloudexec/pkg/config"
15+
"github.com/crytic/cloudexec/pkg/log"
1416
)
1517

1618
func Configure() error {
1719
user, err := user.Current()
1820
if err != nil {
19-
fmt.Printf("Failed to get current user: %v", err)
20-
os.Exit(1)
21+
return fmt.Errorf("Failed to get current user: %v", err)
2122
}
2223
username, err := promptUserInput("Username", user.Username)
2324
if err != nil {
@@ -63,7 +64,7 @@ func Configure() error {
6364
}
6465

6566
func promptSecretInput(prompt, defaultValue string) (string, error) {
66-
fmt.Printf("%s [%s]: ", prompt, defaultValue)
67+
log.Info("%s [%s]: ", prompt, defaultValue)
6768
rawInput, err := term.ReadPassword(int(os.Stdin.Fd()))
6869
if err != nil {
6970
return "", fmt.Errorf("Failed to read input: %w", err)
@@ -81,7 +82,7 @@ func promptSecretInput(prompt, defaultValue string) (string, error) {
8182

8283
func promptUserInput(prompt, defaultValue string) (string, error) {
8384
reader := bufio.NewReader(os.Stdin)
84-
fmt.Printf("%s [%s]: ", prompt, defaultValue)
85+
log.Info("%s [%s]: ", prompt, defaultValue)
8586

8687
input, err := reader.ReadString('\n')
8788
if err != nil {

cmd/cloudexec/init.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
"github.com/crytic/cloudexec/pkg/config"
7+
"github.com/crytic/cloudexec/pkg/log"
78
"github.com/crytic/cloudexec/pkg/s3"
89
)
910

@@ -25,7 +26,7 @@ func Init(config config.Config) error {
2526

2627
if !bucketExists {
2728
// Create a new bucket
28-
fmt.Printf("Creating new %s bucket...\n", bucketName)
29+
log.Wait("Creating new %s bucket", bucketName)
2930
err = s3.CreateBucket(config)
3031
if err != nil {
3132
return err
@@ -56,7 +57,7 @@ func initState(config config.Config, bucketName string) error {
5657
}
5758
// Create the state directory if it does not already exist
5859
if !stateDirExists {
59-
fmt.Printf("Creating new state directory at %s/%s\n", bucketName, stateDir)
60+
log.Wait("Creating new state directory at %s/%s", bucketName, stateDir)
6061
err = s3.PutObject(config, stateDir, []byte{})
6162
if err != nil {
6263
return fmt.Errorf("Failed to create state directory at %s/%s: %w", bucketName, stateDir, err)
@@ -71,7 +72,7 @@ func initState(config config.Config, bucketName string) error {
7172
}
7273
// Create the initial state file if it does not already exist
7374
if !statePathExists {
74-
fmt.Printf("Creating new state file at %s/%s\n", bucketName, statePath)
75+
log.Wait("Creating new state file at %s/%s", bucketName, statePath)
7576
err = s3.PutObject(config, statePath, []byte("{}"))
7677
if err != nil {
7778
return fmt.Errorf("Failed to create state file in bucket %s: %w", bucketName, err)

cmd/cloudexec/launch.go

Lines changed: 29 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/BurntSushi/toml"
1010
"github.com/crytic/cloudexec/pkg/config"
1111
do "github.com/crytic/cloudexec/pkg/digitalocean"
12+
"github.com/crytic/cloudexec/pkg/log"
1213
"github.com/crytic/cloudexec/pkg/ssh"
1314
"github.com/crytic/cloudexec/pkg/state"
1415
)
@@ -43,7 +44,7 @@ func InitLaunchConfig() error {
4344

4445
// Write the default launch config to the file
4546
_, err = launchConfigFile.WriteString(`
46-
# Set the directory to upload to the droplet.
47+
# Set the directory to upload to the server.
4748
[input]
4849
directory = ""
4950
timeout = "48h"
@@ -83,11 +84,8 @@ func LoadLaunchConfig(launchConfigPath string) (LaunchConfig, error) {
8384
return lc, nil
8485
}
8586

86-
func Launch(config config.Config, dropletSize string, dropletRegion string, lc LaunchConfig) error {
87-
username := config.Username
88-
bucketName := fmt.Sprintf("cloudexec-%s", username)
89-
90-
// get existing state from bucket
87+
func Launch(config config.Config, serverSize string, serverRegion string, lc LaunchConfig) error {
88+
// get existing state
9189
existingState, err := state.GetState(config)
9290
if err != nil {
9391
return fmt.Errorf("Failed to get S3 state: %w", err)
@@ -100,90 +98,85 @@ func Launch(config config.Config, dropletSize string, dropletRegion string, lc L
10098
} else {
10199
latestJobId = latestJob.ID
102100
}
103-
thisJobId := latestJobId + 1
101+
jobID := latestJobId + 1
104102

105103
// update state struct with a new job
106104
newState := &state.State{}
107105
startedAt := time.Now().Unix()
108106

109107
newJob := state.Job{
110108
Name: lc.Input.JobName,
111-
ID: thisJobId,
109+
ID: jobID,
112110
Status: state.Provisioning,
113111
StartedAt: startedAt,
114112
}
115113
newState.CreateJob(newJob)
116114
// sync state to bucket
117-
fmt.Printf("Adding new job to the state...\n")
118115
err = state.MergeAndSave(config, newState)
116+
log.Info("Registered new job with id %v", jobID)
119117
if err != nil {
120118
return fmt.Errorf("Failed to update S3 state: %w", err)
121119
}
122120

123121
// upload local files to the bucket
124122
sourcePath := lc.Input.Directory // TODO: verify that this path exists & throw informative error if not
125-
destPath := fmt.Sprintf("job-%v", thisJobId)
126-
fmt.Printf("Compressing and uploading contents of directory %s to bucket %s/%s...\n", sourcePath, bucketName, destPath)
123+
destPath := fmt.Sprintf("job-%v", jobID)
127124
err = UploadDirectoryToSpaces(config, sourcePath, destPath)
128125
if err != nil {
129126
return fmt.Errorf("Failed to upload files: %w", err)
130127
}
131128

132129
// Get or create an SSH key
133-
fmt.Println("Getting or creating SSH key pair...")
134130
publicKey, err := ssh.GetOrCreateSSHKeyPair()
135131
if err != nil {
136132
return fmt.Errorf("Failed to get or creating SSH key pair: %w", err)
137133
}
138134

139135
// Prepare user data
140-
fmt.Println("Generating user data...")
141136
userData, err := GenerateUserData(config, lc)
142137
if err != nil {
143138
return fmt.Errorf("Failed to generate user data: %w", err)
144139
}
145140

146-
fmt.Printf("Creating new %s droplet in %s for job %d...\n", dropletSize, config.DigitalOcean.SpacesRegion, thisJobId)
147-
droplet, err := do.CreateDroplet(config, config.DigitalOcean.SpacesRegion, dropletSize, userData, thisJobId, publicKey)
141+
log.Wait("Creating new %s server in %s for job %d", serverSize, config.DigitalOcean.SpacesRegion, jobID)
142+
server, err := do.CreateDroplet(config, config.DigitalOcean.SpacesRegion, serverSize, userData, jobID, publicKey)
148143
if err != nil {
149-
return fmt.Errorf("Failed to create droplet: %w", err)
144+
return fmt.Errorf("Failed to create server: %w", err)
150145
}
146+
log.Good("Server created with IP: %v", server.IP)
151147

152-
fmt.Printf("Droplet created with IP: %v\n", droplet.IP)
153-
154-
// Add the droplet info to state
155-
fmt.Println("Adding new droplet info to state...")
148+
// Add the server info to state
156149
updatedAt := time.Now().Unix()
157150
for i, job := range newState.Jobs {
158-
if job.ID == thisJobId {
159-
newState.Jobs[i].Droplet = droplet
151+
if job.ID == jobID {
152+
newState.Jobs[i].Droplet = server
160153
newState.Jobs[i].UpdatedAt = updatedAt
161154
}
162155
}
163-
fmt.Printf("Uploading new state to %s\n", bucketName)
164156
err = state.MergeAndSave(config, newState)
165157
if err != nil {
166158
return fmt.Errorf("Failed to update S3 state: %w", err)
167159
}
160+
log.Info("Saved new server info to state")
168161

169-
// Add the droplet to the SSH config file
170-
fmt.Println("Adding droplet to SSH config file...")
171-
err = ssh.AddSSHConfig(thisJobId, droplet.IP)
162+
// Add the server to the SSH config file
163+
err = ssh.AddSSHConfig(jobID, server.IP)
172164
if err != nil {
173-
return fmt.Errorf("Failed to add droplet to SSH config file: %w", err)
165+
return fmt.Errorf("Failed to add server to SSH config file: %w", err)
174166
}
167+
log.Info("Added cloudexec-%v to SSH config", jobID)
175168

176-
// Ensure we can SSH into the droplet
177-
fmt.Println("Ensuring we can SSH into the droplet...")
178-
err = ssh.WaitForSSHConnection(thisJobId)
169+
// Ensure we can SSH into the server
170+
log.Wait("Waiting for our new server to wake up")
171+
err = ssh.WaitForSSHConnection(jobID)
179172
if err != nil {
180-
return fmt.Errorf("Failed to SSH into the droplet: %w", err)
173+
return fmt.Errorf("Failed to SSH into the server: %w", err)
181174
}
182-
fmt.Println("SSH connection established!")
183-
fmt.Println("Launch complete")
184-
fmt.Println("You can now attach to the running job with: cloudexec attach")
185-
fmt.Println("Stream logs from the droplet with: cloudexec logs")
186-
fmt.Println("SSH to your droplet with: ssh cloudexec")
175+
log.Good("Good Morning!")
176+
fmt.Println()
177+
log.Info("Stream logs from the server with: cloudexec logs")
178+
log.Info("SSH to your server with: ssh cloudexec-%v", jobID)
179+
log.Info("Once setup is complete, you can attach to the running job with: cloudexec attach")
187180

188181
return nil
189182
}

0 commit comments

Comments
 (0)