Skip to content

Commit 7c45b57

Browse files
committed
Improve error handling and logging
1 parent 9e2d21d commit 7c45b57

File tree

18 files changed

+537
-238
lines changed

18 files changed

+537
-238
lines changed

internal/commands/down.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package commands
22

33
import (
4+
"fmt"
45
"log/slog"
56

67
"github.com/eikendev/hackenv/internal/handling"
@@ -14,18 +15,31 @@ type DownCommand struct{}
1415

1516
// Run is the function for the down command.
1617
func (c *DownCommand) Run(s *options.Options) error {
17-
image := images.GetImageDetails(s.Type)
18+
image, err := images.GetImageDetails(s.Type)
19+
if err != nil {
20+
slog.Error("Failed to get image details for down command", "type", s.Type, "err", err)
21+
return fmt.Errorf("cannot resolve image details for %q: %w", s.Type, err)
22+
}
1823

19-
conn := libvirt.Connect()
24+
conn, err := libvirt.Connect()
25+
if err != nil {
26+
slog.Error("Failed to connect to libvirt for down command", "err", err)
27+
return fmt.Errorf("cannot connect to libvirt: %w", err)
28+
}
2029
defer handling.CloseConnect(conn)
2130

22-
dom := libvirt.GetDomain(conn, &image, true)
23-
defer handling.FreeDomain(dom)
24-
25-
err := dom.Destroy()
31+
dom, err := libvirt.GetDomain(conn, &image, true)
2632
if err != nil {
33+
slog.Error("Failed to lookup domain for down command", "image", image.Name, "err", err)
34+
return fmt.Errorf("cannot look up domain %q: %w", image.Name, err)
35+
}
36+
if dom != nil {
37+
defer handling.FreeDomain(dom)
38+
}
39+
40+
if err := dom.Destroy(); err != nil {
2741
slog.Error("Cannot destroy domain", "err", err)
28-
return err
42+
return fmt.Errorf("cannot destroy domain %q: %w", image.Name, err)
2943
}
3044

3145
return nil

internal/commands/fix.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ func execCommand(scripts []string, verbose bool) error {
5353
fmt.Println(string(b))
5454
}
5555
if err != nil {
56-
return err
56+
slog.Error("Script execution failed", "index", i+1, "total", len(scripts), "err", err)
57+
return fmt.Errorf("failed to run script %d/%d: %w", i+1, len(scripts), err)
5758
}
5859
}
5960

internal/commands/get.go

Lines changed: 66 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package commands
22

33
import (
44
"crypto/sha256"
5+
"errors"
56
"fmt"
67
"io"
78
"log/slog"
@@ -28,14 +29,14 @@ func calculateFileChecksum(path string) (string, error) {
2829
f, err := os.Open(path) //#nosec G304
2930
if err != nil {
3031
slog.Error("Failed to open file", "path", path, "err", err)
31-
return "", err
32+
return "", fmt.Errorf("cannot open %s: %w", path, err)
3233
}
3334
defer handling.Close(f)
3435

3536
h := sha256.New()
3637
if _, err := io.Copy(h, f); err != nil {
3738
slog.Error("Failed to copy file content", "path", path, "err", err)
38-
return "", err
39+
return "", fmt.Errorf("cannot read %s for checksum: %w", path, err)
3940
}
4041

4142
return fmt.Sprintf("%x", h.Sum(nil)), nil
@@ -48,14 +49,14 @@ func downloadImage(path, url string) error {
4849
out, err := os.Create(path) //#nosec G304
4950
if err != nil {
5051
slog.Error("Cannot create image file", "path", path, "err", err)
51-
return err
52+
return fmt.Errorf("cannot create %s: %w", path, err)
5253
}
5354
defer handling.Close(out)
5455

5556
resp, err := http.Get(url) //#nosec G107
5657
if err != nil {
5758
slog.Error("Cannot download image file", "url", url, "err", err)
58-
return err
59+
return fmt.Errorf("cannot download %s: %w", url, err)
5960
}
6061
if resp == nil {
6162
return fmt.Errorf("received nil response")
@@ -64,7 +65,7 @@ func downloadImage(path, url string) error {
6465

6566
if resp.StatusCode != http.StatusOK {
6667
slog.Error("Cannot download image file: bad status", "status", resp.Status)
67-
return err
68+
return fmt.Errorf("cannot download %s: status %s", url, resp.Status)
6869
}
6970

7071
bar := progressbar.DefaultBytes(
@@ -75,7 +76,7 @@ func downloadImage(path, url string) error {
7576
_, err = io.Copy(io.MultiWriter(out, bar), resp.Body)
7677
if err != nil {
7778
slog.Error("Cannot write image file", "path", path, "err", err)
78-
return err
79+
return fmt.Errorf("cannot write %s: %w", path, err)
7980
}
8081

8182
slog.Info("Download successful")
@@ -85,18 +86,19 @@ func downloadImage(path, url string) error {
8586
func validateChecksum(localPath, checksum string) error {
8687
newChecksum, err := calculateFileChecksum(localPath)
8788
if err != nil {
88-
return err
89+
slog.Error("Failed to calculate checksum for downloaded image", "path", localPath, "err", err)
90+
return fmt.Errorf("failed to calculate checksum for %s: %w", localPath, err)
8991
}
9092

9193
if newChecksum != checksum {
92-
err := os.Remove(localPath)
93-
if err != nil {
94-
slog.Error("Downloaded image has bad checksum and cannot be removed", "path", localPath, "expected", checksum, "actual", newChecksum, "err", err)
95-
os.Exit(1)
94+
removeErr := os.Remove(localPath)
95+
if removeErr != nil {
96+
slog.Error("Downloaded image has bad checksum and cannot be removed", "path", localPath, "expected", checksum, "actual", newChecksum, "err", removeErr)
97+
return fmt.Errorf("failed to remove image with bad checksum (expected %s, actual %s): %w", checksum, newChecksum, removeErr)
9698
}
9799

98100
slog.Error("Downloaded image has bad checksum and was removed", "path", localPath, "expected", checksum, "actual", newChecksum)
99-
os.Exit(1)
101+
return fmt.Errorf("detected bad checksum for downloaded image: expected %s, actual %s", checksum, newChecksum)
100102
}
101103

102104
slog.Info("Checksum validated successfully")
@@ -105,47 +107,77 @@ func validateChecksum(localPath, checksum string) error {
105107

106108
// Run is the function for the get command.
107109
func (c *GetCommand) Run(s *options.Options) error {
108-
image := images.GetImageDetails(s.Type)
109-
info := image.GetDownloadInfo(true)
110+
image, err := images.GetImageDetails(s.Type)
111+
if err != nil {
112+
slog.Error("Failed to get image details for get command", "type", s.Type, "err", err)
113+
return fmt.Errorf("cannot resolve image details for %q: %w", s.Type, err)
114+
}
115+
info, err := image.GetDownloadInfo(true)
116+
if err != nil {
117+
slog.Error("Failed to get download info for image", "image", image.DisplayName, "err", err)
118+
return fmt.Errorf("cannot fetch download info for %s: %w", image.DisplayName, err)
119+
}
110120
if info == nil {
121+
slog.Error("Download info is nil", "image", image.DisplayName)
111122
return fmt.Errorf("failed to get download information")
112123
}
113124

114125
slog.Info("Found image to download", "filename", info.Filename, "checksum", info.Checksum)
115126

116-
localPath := image.GetLocalPath(info.Version)
127+
localPath, err := image.GetLocalPath(info.Version)
128+
if err != nil {
129+
slog.Error("Failed to resolve local image path", "image", image.DisplayName, "version", info.Version, "err", err)
130+
return fmt.Errorf("cannot resolve local path for %s %s: %w", image.DisplayName, info.Version, err)
131+
}
117132

118-
// https://stackoverflow.com/a/12518877
119-
if _, err := os.Stat(localPath); err == nil {
120-
// The image already exists.
133+
skipDownload, err := c.shouldSkipDownload(localPath, image, info)
134+
if err != nil {
135+
slog.Error("Failed to determine if download should be skipped", "path", localPath, "err", err)
136+
return fmt.Errorf("cannot decide whether to download %s: %w", localPath, err)
137+
}
138+
if skipDownload {
139+
return nil
140+
}
141+
142+
err = downloadImage(localPath, image.ArchiveURL+"/"+info.Filename)
143+
if err != nil {
144+
slog.Error("Failed to download image", "path", localPath, "url", image.ArchiveURL+"/"+info.Filename, "err", err)
145+
return fmt.Errorf("cannot download image %s/%s: %w", image.ArchiveURL, info.Filename, err)
146+
}
121147

148+
err = validateChecksum(localPath, info.Checksum)
149+
if err != nil {
150+
slog.Error("Checksum validation failed", "path", localPath, "err", err)
151+
return fmt.Errorf("failed to validate checksum for %s: %w", localPath, err)
152+
}
153+
154+
slog.Info("When using SELinux, label the image with the fix command before proceeding")
155+
156+
return nil
157+
}
158+
159+
func (c *GetCommand) shouldSkipDownload(localPath string, image images.Image, info *images.DownloadInfo) (bool, error) {
160+
// https://stackoverflow.com/a/12518877
161+
_, err := os.Stat(localPath)
162+
if err == nil {
122163
if !c.Update && !c.Force {
123164
slog.Info("An image is already installed; use --update to refresh")
124-
return nil
165+
return true, nil
125166
}
126167

127168
localVersion := image.FileVersion(localPath)
128169

129170
if !c.Force && image.VersionComparer.Eq(info.Version, localVersion) {
130171
slog.Info("Latest image is already installed; use --force to overwrite")
131-
return nil
172+
return true, nil
132173
}
133-
} else if !os.IsNotExist(err) {
134-
slog.Error("Unable to get file information", "path", localPath, "err", err)
135-
os.Exit(1)
174+
return false, nil
136175
}
137176

138-
err := downloadImage(localPath, image.ArchiveURL+"/"+info.Filename)
139-
if err != nil {
140-
return err
177+
if errors.Is(err, os.ErrNotExist) {
178+
return false, nil
141179
}
142180

143-
err = validateChecksum(localPath, info.Checksum)
144-
if err != nil {
145-
return err
146-
}
147-
148-
slog.Info("When using SELinux, label the image with the fix command before proceeding")
149-
150-
return nil
181+
slog.Error("Unable to get file information", "path", localPath, "err", err)
182+
return false, fmt.Errorf("cannot stat %s: %w", localPath, err)
151183
}

internal/commands/gui.go

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,38 +27,31 @@ type GuiCommand struct {
2727

2828
// Run is the function for the gui command.
2929
func (c *GuiCommand) Run(s *options.Options) error {
30-
image := images.GetImageDetails(s.Type)
30+
image, err := images.GetImageDetails(s.Type)
31+
if err != nil {
32+
slog.Error("Failed to get image details for GUI command", "type", s.Type, "err", err)
33+
return fmt.Errorf("cannot resolve image details for %q: %w", s.Type, err)
34+
}
3135

32-
conn := libvirt.Connect()
36+
conn, err := libvirt.Connect()
37+
if err != nil {
38+
slog.Error("Failed to connect to libvirt for GUI command", "err", err)
39+
return fmt.Errorf("cannot connect to libvirt: %w", err)
40+
}
3341
defer handling.CloseConnect(conn)
3442

3543
// Check if the domain is up.
36-
dom := libvirt.GetDomain(conn, &image, true)
44+
dom, err := libvirt.GetDomain(conn, &image, true)
45+
if err != nil {
46+
slog.Error("Failed to lookup domain for GUI command", "image", image.Name, "err", err)
47+
return fmt.Errorf("cannot look up domain %q: %w", image.Name, err)
48+
}
3749
defer handling.FreeDomain(dom)
3850

39-
var args []string
40-
41-
if virtViewerPath, err := paths.GetCmdPath(virtViewerBin); c.Viewer == virtViewerBin && err == nil {
42-
args = []string{
43-
virtViewerPath,
44-
"--connect",
45-
constants.ConnectURI,
46-
image.Name,
47-
}
48-
49-
if c.Fullscreen {
50-
args = append(args, []string{"--full-screen"}...)
51-
}
52-
53-
} else if remminaPath, err := paths.GetCmdPath(remminaBin); c.Viewer == remminaBin && err == nil {
54-
args = []string{
55-
remminaPath,
56-
"-c",
57-
"SPICE://localhost",
58-
}
59-
} else {
60-
slog.Error("Unable to locate viewer to connect to the VM", "viewer", c.Viewer)
61-
return fmt.Errorf("unable to locate %s to connect to the VM", c.Viewer)
51+
args, err := c.viewerArgs(&image)
52+
if err != nil {
53+
slog.Error("Failed to resolve viewer arguments", "viewer", c.Viewer, "err", err)
54+
return fmt.Errorf("cannot resolve viewer %q: %w", c.Viewer, err)
6255
}
6356

6457
cwd, err := os.Getwd()
@@ -73,8 +66,46 @@ func (c *GuiCommand) Run(s *options.Options) error {
7366
err = cmd.Start()
7467
if err != nil {
7568
slog.Error("Cannot spawn viewer process", "err", err)
69+
return fmt.Errorf("cannot start viewer %q: %w", args[0], err)
7670
}
7771
defer handling.ReleaseProcess(cmd.Process)
7872

7973
return nil
8074
}
75+
76+
func (c *GuiCommand) viewerArgs(image *images.Image) ([]string, error) {
77+
switch c.Viewer {
78+
case virtViewerBin:
79+
virtViewerPath, err := paths.GetCmdPath(virtViewerBin)
80+
if err != nil {
81+
slog.Error("Unable to locate viewer", "viewer", c.Viewer, "err", err)
82+
return nil, fmt.Errorf("unable to locate %s", c.Viewer)
83+
}
84+
85+
args := []string{
86+
virtViewerPath,
87+
"--connect",
88+
constants.ConnectURI,
89+
image.Name,
90+
}
91+
if c.Fullscreen {
92+
args = append(args, "--full-screen")
93+
}
94+
return args, nil
95+
case remminaBin:
96+
remminaPath, err := paths.GetCmdPath(remminaBin)
97+
if err != nil {
98+
slog.Error("Unable to locate viewer", "viewer", c.Viewer, "err", err)
99+
return nil, fmt.Errorf("unable to locate %s", c.Viewer)
100+
}
101+
102+
return []string{
103+
remminaPath,
104+
"-c",
105+
"SPICE://localhost",
106+
}, nil
107+
default:
108+
slog.Error("Unsupported viewer", "viewer", c.Viewer)
109+
return nil, fmt.Errorf("cannot use viewer %s: unsupported", c.Viewer)
110+
}
111+
}

0 commit comments

Comments
 (0)