-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmain.go
188 lines (153 loc) · 4.49 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package main
import (
"archive/tar"
"archive/zip"
"bytes"
"fmt"
"io"
"io/fs"
"log/slog"
"os"
"time"
"github.com/alecthomas/kong"
)
var CLI struct {
Input string `arg:"" name:"input" help:"Input file."`
Target string `arg:"" name:"target" help:"Target destination in the filesystem."`
Output string `arg:"" name:"output" help:"Output file."`
Type string `short:"t" name:"type" default:"tar" help:"Type of the archive. (tar, zip)"`
Verbose bool `short:"v" name:"verbose" help:"Enable verbose output."`
}
func main() {
// ctx := kong.Parse(&cli,
kong.Parse(&CLI,
kong.Name("golinkwrite"),
kong.Description("Create a tar archive containing a provided file and a symlink that points to the write destination."),
kong.UsageOnError(),
kong.Vars{"version": "0.1.0"},
)
// Check for verbose output
logLevel := slog.LevelError
if CLI.Verbose {
logLevel = slog.LevelDebug
}
// setup logger
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: logLevel,
}))
// log parameters
logger.Debug("command line parameters", "cli", CLI)
// get file mode from input file
fileInfo, err := os.Stat(CLI.Input)
if err != nil {
logger.Error("failed to get file info", "error", err)
os.Exit(1)
}
logger.Debug("input permissions", "perm", fileInfo.Mode().Perm())
// read the input file
content, err := os.ReadFile(CLI.Input)
if err != nil {
logger.Error("failed to read input file", "error", err)
os.Exit(1)
}
logger.Debug("input size", "size", len(content))
// prepare the tar file
out, err := os.Create(CLI.Output)
if err != nil {
logger.Error("failed to create output file", "error", err)
os.Exit(1)
}
switch CLI.Type {
case "tar":
if err := createTar(out, fileInfo, content); err != nil {
panic(fmt.Errorf("failed to create tar file: %w", err))
}
case "zip":
if err := createZip(out, fileInfo, content); err != nil {
panic(fmt.Errorf("failed to create zip file: %w", err))
}
default:
panic(fmt.Errorf("unsupported archive type: %s", CLI.Type))
}
logger.Info("archive created", "output", CLI.Output)
}
func createTar(out io.Writer, fi fs.FileInfo, content []byte) error {
// create writer add a file and a directory
writer := tar.NewWriter(out)
defer func() {
if err := writer.Close(); err != nil {
panic(fmt.Errorf("failed to close tar writer: %w", err))
}
}()
// add a symlink to the tar file
header := &tar.Header{
Name: CLI.Input,
Linkname: CLI.Target,
Mode: int64(fi.Mode().Perm()),
Typeflag: tar.TypeSymlink,
Size: 0,
}
if err := writer.WriteHeader(header); err != nil {
return fmt.Errorf("failed to write header to tar file: %w", err)
}
// add a file header
header = &tar.Header{
Name: CLI.Input,
Mode: int64(fi.Mode().Perm()),
Size: int64(len(content)),
}
if err := writer.WriteHeader(header); err != nil {
return fmt.Errorf("failed to write header to tar file: %w", err)
}
// add the file content
if _, err := writer.Write([]byte(content)); err != nil {
return fmt.Errorf("failed to write file to tar file: %w", err)
}
return nil
}
func createZip(out io.Writer, fi fs.FileInfo, content []byte) error {
// create zip writer
writer := zip.NewWriter(out)
defer func() {
if err := writer.Close(); err != nil {
panic(fmt.Errorf("failed to close zip writer: %w", err))
}
}()
// create a new file header
zipHeader := &zip.FileHeader{
Name: CLI.Input,
Method: zip.Store,
Modified: time.Now(),
}
zipHeader.SetMode(os.ModeSymlink | 0755)
// create a new file writer
fw, err := writer.CreateHeader(zipHeader)
if err != nil {
return fmt.Errorf("failed to create zip header for symlink %s: %s", CLI.Input, err)
}
// write the symlink to the zip archive
if _, err := fw.Write([]byte(CLI.Target)); err != nil {
return fmt.Errorf("failed to write symlink target %s to zip archive: %s", CLI.Target, err)
}
// create a new file header
zipHeader, err = zip.FileInfoHeader(fi)
if err != nil {
return fmt.Errorf("failed to create file header: %s", err)
}
// set the name of the file
zipHeader.Name = CLI.Input
// set the method of compression
zipHeader.Method = zip.Deflate
// create a new file writer
zw, err := writer.CreateHeader(zipHeader)
if err != nil {
return fmt.Errorf("failed to create zip file header: %s", err)
}
// write the file to the zip archive
// create reader for byte slice
reader := bytes.NewReader(content)
if _, err := io.Copy(zw, reader); err != nil {
return fmt.Errorf("failed to write file to zip archive: %s", err)
}
return nil
}