Skip to content

Commit 720baae

Browse files
authored
feat: Add Checksums to package.json format (#1217)
Adds zip file checksums to the package.json format. Includes some commits from #1216 to avoid merge conflicts later.
1 parent 70f20bb commit 720baae

File tree

3 files changed

+159
-63
lines changed

3 files changed

+159
-63
lines changed

serve/package.go

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"archive/zip"
55
"bytes"
66
"context"
7+
"crypto/sha256"
78
"encoding/json"
89
"fmt"
910
"io"
@@ -31,16 +32,18 @@ This creates a directory with the plugin binaries, package.json and documentatio
3132
type PackageJSON struct {
3233
SchemaVersion int `json:"schema_version"`
3334
Name string `json:"name"`
35+
Message string `json:"message"`
3436
Version string `json:"version"`
3537
Protocols []int `json:"protocols"`
3638
SupportedTargets []TargetBuild `json:"supported_targets"`
3739
PackageType plugin.PackageType `json:"package_type"`
3840
}
3941

4042
type TargetBuild struct {
41-
OS string `json:"os"`
42-
Arch string `json:"arch"`
43-
Path string `json:"path"`
43+
OS string `json:"os"`
44+
Arch string `json:"arch"`
45+
Path string `json:"path"`
46+
Checksum string `json:"checksum"`
4447
}
4548

4649
func (s *PluginServe) writeTablesJSON(ctx context.Context, dir string) error {
@@ -62,13 +65,13 @@ func (s *PluginServe) writeTablesJSON(ctx context.Context, dir string) error {
6265
return os.WriteFile(outputPath, buffer.Bytes(), 0644)
6366
}
6467

65-
func (s *PluginServe) build(pluginDirectory, goos, goarch, distPath, pluginVersion string) error {
68+
func (s *PluginServe) build(pluginDirectory, goos, goarch, distPath, pluginVersion string) (*TargetBuild, error) {
6669
pluginName := fmt.Sprintf("plugin-%s-%s-%s-%s", s.plugin.Name(), pluginVersion, goos, goarch)
6770
pluginPath := path.Join(distPath, pluginName)
6871
args := []string{"build", "-o", pluginPath}
6972
importPath, err := s.getModuleName(pluginDirectory)
7073
if err != nil {
71-
return err
74+
return nil, err
7275
}
7376
args = append(args, "-buildmode=exe")
7477
args = append(args, "-ldflags", fmt.Sprintf("-s -w -X %s/plugin.Version=%s", importPath, pluginVersion))
@@ -78,19 +81,19 @@ func (s *PluginServe) build(pluginDirectory, goos, goarch, distPath, pluginVersi
7881
cmd.Stderr = os.Stderr
7982
cmd.Env = os.Environ()
8083
if err := cmd.Run(); err != nil {
81-
return fmt.Errorf("failed to build plugin with `go %v`: %w", args, err)
84+
return nil, fmt.Errorf("failed to build plugin with `go %v`: %w", args, err)
8285
}
8386

8487
pluginFile, err := os.Open(pluginPath)
8588
if err != nil {
86-
return fmt.Errorf("failed to open plugin file: %w", err)
89+
return nil, fmt.Errorf("failed to open plugin file: %w", err)
8790
}
8891
defer pluginFile.Close()
8992

9093
zipPluginPath := pluginPath + ".zip"
9194
zipPluginFile, err := os.Create(zipPluginPath)
9295
if err != nil {
93-
return fmt.Errorf("failed to create zip file: %w", err)
96+
return nil, fmt.Errorf("failed to create zip file: %w", err)
9497
}
9598
defer zipPluginFile.Close()
9699

@@ -99,19 +102,52 @@ func (s *PluginServe) build(pluginDirectory, goos, goarch, distPath, pluginVersi
99102

100103
pluginZip, err := zipWriter.Create(pluginName)
101104
if err != nil {
102-
return fmt.Errorf("failed to create file in zip archive: %w", err)
105+
zipWriter.Close()
106+
return nil, fmt.Errorf("failed to create file in zip archive: %w", err)
103107
}
104108
_, err = io.Copy(pluginZip, pluginFile)
105109
if err != nil {
106-
return fmt.Errorf("failed to copy plugin file to zip archive: %w", err)
110+
zipWriter.Close()
111+
return nil, fmt.Errorf("failed to copy plugin file to zip archive: %w", err)
112+
}
113+
err = zipWriter.Close()
114+
if err != nil {
115+
return nil, fmt.Errorf("failed to close zip archive: %w", err)
107116
}
117+
108118
if err := pluginFile.Close(); err != nil {
109-
return err
119+
return nil, err
110120
}
111121
if err := os.Remove(pluginPath); err != nil {
112-
return fmt.Errorf("failed to remove plugin file: %w", err)
122+
return nil, fmt.Errorf("failed to remove plugin file: %w", err)
113123
}
114-
return nil
124+
125+
targetZip := fmt.Sprintf(pluginName + ".zip")
126+
checksum, err := calcChecksum(path.Join(distPath, targetZip))
127+
if err != nil {
128+
return nil, fmt.Errorf("failed to calculate checksum: %w", err)
129+
}
130+
131+
return &TargetBuild{
132+
OS: goos,
133+
Arch: goarch,
134+
Path: targetZip,
135+
Checksum: "sha256:" + checksum,
136+
}, nil
137+
}
138+
139+
func calcChecksum(p string) (string, error) {
140+
// calculate SHA-256 checksum
141+
f, err := os.Open(p)
142+
if err != nil {
143+
return "", fmt.Errorf("failed to open file: %w", err)
144+
}
145+
defer f.Close()
146+
hash := sha256.New()
147+
if _, err := io.Copy(hash, f); err != nil {
148+
return "", err
149+
}
150+
return fmt.Sprintf("%x", hash.Sum(nil)), nil
115151
}
116152

117153
func (*PluginServe) getModuleName(pluginDirectory string) (string, error) {
@@ -131,19 +167,11 @@ func (*PluginServe) getModuleName(pluginDirectory string) (string, error) {
131167
return strings.TrimSpace(importPath), nil
132168
}
133169

134-
func (s *PluginServe) writePackageJSON(dir, pluginVersion string) error {
135-
targets := []TargetBuild{}
136-
for _, target := range s.plugin.Targets() {
137-
pluginName := fmt.Sprintf("plugin-%s-%s-%s-%s", s.plugin.Name(), pluginVersion, target.OS, target.Arch)
138-
targets = append(targets, TargetBuild{
139-
OS: target.OS,
140-
Arch: target.Arch,
141-
Path: pluginName + ".zip",
142-
})
143-
}
170+
func (s *PluginServe) writePackageJSON(dir, pluginVersion, message string, targets []TargetBuild) error {
144171
packageJSON := PackageJSON{
145172
SchemaVersion: 1,
146173
Name: s.plugin.Name(),
174+
Message: message,
147175
Version: pluginVersion,
148176
Protocols: s.versions,
149177
SupportedTargets: targets,
@@ -206,7 +234,7 @@ func copyFile(src, dst string) error {
206234

207235
func (s *PluginServe) newCmdPluginPackage() *cobra.Command {
208236
cmd := &cobra.Command{
209-
Use: "package <plugin_directory> <version>",
237+
Use: "package -m <message> <plugin_directory> <version>",
210238
Short: pluginPackageShort,
211239
Long: pluginPackageLong,
212240
Args: cobra.ExactArgs(2),
@@ -221,6 +249,21 @@ func (s *PluginServe) newCmdPluginPackage() *cobra.Command {
221249
if cmd.Flag("docs-dir").Changed {
222250
docsPath = cmd.Flag("docs-dir").Value.String()
223251
}
252+
message := ""
253+
if !cmd.Flag("message").Changed {
254+
return fmt.Errorf("message is required")
255+
}
256+
message = cmd.Flag("message").Value.String()
257+
if strings.HasPrefix(message, "@") {
258+
messageFile := strings.TrimPrefix(message, "@")
259+
messageBytes, err := os.ReadFile(messageFile)
260+
if err != nil {
261+
return err
262+
}
263+
message = string(messageBytes)
264+
}
265+
message = normalizeMessage(message)
266+
224267
if err := os.MkdirAll(distPath, 0755); err != nil {
225268
return err
226269
}
@@ -232,13 +275,16 @@ func (s *PluginServe) newCmdPluginPackage() *cobra.Command {
232275
if err := s.writeTablesJSON(cmd.Context(), distPath); err != nil {
233276
return err
234277
}
278+
targets := []TargetBuild{}
235279
for _, target := range s.plugin.Targets() {
236280
fmt.Println("Building for OS: " + target.OS + ", ARCH: " + target.Arch)
237-
if err := s.build(pluginDirectory, target.OS, target.Arch, distPath, pluginVersion); err != nil {
281+
targetBuild, err := s.build(pluginDirectory, target.OS, target.Arch, distPath, pluginVersion)
282+
if err != nil {
238283
return fmt.Errorf("failed to build plugin for %s/%s: %w", target.OS, target.Arch, err)
239284
}
285+
targets = append(targets, *targetBuild)
240286
}
241-
if err := s.writePackageJSON(distPath, pluginVersion); err != nil {
287+
if err := s.writePackageJSON(distPath, pluginVersion, message, targets); err != nil {
242288
return fmt.Errorf("failed to write manifest: %w", err)
243289
}
244290
if err := s.copyDocs(distPath, docsPath); err != nil {
@@ -249,5 +295,13 @@ func (s *PluginServe) newCmdPluginPackage() *cobra.Command {
249295
}
250296
cmd.Flags().StringP("dist-dir", "D", "", "dist directory to output the built plugin. (default: <plugin_directory>/dist)")
251297
cmd.Flags().StringP("docs-dir", "", "", "docs directory containing markdown files to copy to the dist directory. (default: <plugin_directory>/docs)")
298+
cmd.Flags().StringP("message", "m", "", "message that summarizes what is new or changed in this version. Use @<file> to read from file. Supports markdown.")
252299
return cmd
253300
}
301+
302+
func normalizeMessage(s string) string {
303+
s = strings.TrimSpace(s)
304+
s = strings.ReplaceAll(s, "\r\n", "\n")
305+
s = strings.ReplaceAll(s, "\r", "\n")
306+
return s
307+
}

serve/package_test.go

Lines changed: 77 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package serve
22

33
import (
4+
"crypto/sha256"
45
"encoding/json"
6+
"fmt"
57
"io"
68
"os"
79
"path/filepath"
@@ -31,48 +33,86 @@ func TestPluginPackage(t *testing.T) {
3133
{OS: plugin.GoOSDarwin, Arch: plugin.GoArchAmd64},
3234
}),
3335
)
34-
srv := Plugin(p)
35-
cmd := srv.newCmdPluginRoot()
36-
distDir := t.TempDir()
37-
cmd.SetArgs([]string{"package", "--dist-dir", distDir, simplePluginPath, packageVersion})
38-
if err := cmd.Execute(); err != nil {
39-
t.Fatal(err)
40-
}
41-
files, err := os.ReadDir(distDir)
42-
if err != nil {
43-
t.Fatal(err)
44-
}
45-
expect := []string{
46-
"docs",
47-
"package.json",
48-
"plugin-testPlugin-v1.2.3-darwin-amd64.zip",
49-
"plugin-testPlugin-v1.2.3-linux-amd64.zip",
50-
"plugin-testPlugin-v1.2.3-windows-amd64.zip",
51-
"tables.json",
52-
}
53-
if diff := cmp.Diff(expect, fileNames(files)); diff != "" {
54-
t.Fatalf("unexpected files in dist directory (-want +got):\n%s", diff)
36+
msg := `Test message
37+
with multiple lines and **markdown**`
38+
testCases := []struct {
39+
name string
40+
message string
41+
wantErr bool
42+
}{
43+
{
44+
name: "inline message",
45+
message: msg,
46+
},
47+
{
48+
name: "message from file",
49+
message: "@testdata/message.txt",
50+
},
5551
}
52+
for _, tc := range testCases {
53+
t.Run(tc.name, func(t *testing.T) {
54+
srv := Plugin(p)
55+
cmd := srv.newCmdPluginRoot()
56+
distDir := t.TempDir()
57+
cmd.SetArgs([]string{"package", "--dist-dir", distDir, "-m", tc.message, simplePluginPath, packageVersion})
58+
err := cmd.Execute()
59+
if tc.wantErr && err == nil {
60+
t.Fatalf("expected error, got nil")
61+
} else if err != nil {
62+
t.Fatalf("unexpected error: %v", err)
63+
}
64+
files, err := os.ReadDir(distDir)
65+
if err != nil {
66+
t.Fatal(err)
67+
}
68+
expect := []string{
69+
"docs",
70+
"package.json",
71+
"plugin-testPlugin-v1.2.3-darwin-amd64.zip",
72+
"plugin-testPlugin-v1.2.3-linux-amd64.zip",
73+
"plugin-testPlugin-v1.2.3-windows-amd64.zip",
74+
"tables.json",
75+
}
76+
if diff := cmp.Diff(expect, fileNames(files)); diff != "" {
77+
t.Fatalf("unexpected files in dist directory (-want +got):\n%s", diff)
78+
}
5679

57-
expectPackage := PackageJSON{
58-
SchemaVersion: 1,
59-
Name: "testPlugin",
60-
Version: "v1.2.3",
61-
Protocols: []int{3},
62-
SupportedTargets: []TargetBuild{
63-
{OS: plugin.GoOSLinux, Arch: plugin.GoArchAmd64, Path: "plugin-testPlugin-v1.2.3-linux-amd64.zip"},
64-
{OS: plugin.GoOSWindows, Arch: plugin.GoArchAmd64, Path: "plugin-testPlugin-v1.2.3-windows-amd64.zip"},
65-
{OS: plugin.GoOSDarwin, Arch: plugin.GoArchAmd64, Path: "plugin-testPlugin-v1.2.3-darwin-amd64.zip"},
66-
},
67-
PackageType: plugin.PackageTypeNative,
80+
expectPackage := PackageJSON{
81+
SchemaVersion: 1,
82+
Name: "testPlugin",
83+
Message: msg,
84+
Version: "v1.2.3",
85+
Protocols: []int{3},
86+
SupportedTargets: []TargetBuild{
87+
{OS: plugin.GoOSLinux, Arch: plugin.GoArchAmd64, Path: "plugin-testPlugin-v1.2.3-linux-amd64.zip", Checksum: "sha256:" + sha256sum(filepath.Join(distDir, "plugin-testPlugin-v1.2.3-linux-amd64.zip"))},
88+
{OS: plugin.GoOSWindows, Arch: plugin.GoArchAmd64, Path: "plugin-testPlugin-v1.2.3-windows-amd64.zip", Checksum: "sha256:" + sha256sum(filepath.Join(distDir, "plugin-testPlugin-v1.2.3-windows-amd64.zip"))},
89+
{OS: plugin.GoOSDarwin, Arch: plugin.GoArchAmd64, Path: "plugin-testPlugin-v1.2.3-darwin-amd64.zip", Checksum: "sha256:" + sha256sum(filepath.Join(distDir, "plugin-testPlugin-v1.2.3-darwin-amd64.zip"))},
90+
},
91+
PackageType: plugin.PackageTypeNative,
92+
}
93+
checkPackageJSONContents(t, filepath.Join(distDir, "package.json"), expectPackage)
94+
95+
expectDocs := []string{
96+
"configuration.md",
97+
"overview.md",
98+
}
99+
checkDocs(t, filepath.Join(distDir, "docs"), expectDocs)
100+
})
68101
}
69-
checkPackageJSONContents(t, filepath.Join(distDir, "package.json"), expectPackage)
102+
}
70103

71-
expectDocs := []string{
72-
"configuration.md",
73-
"overview.md",
104+
func sha256sum(filename string) string {
105+
f, err := os.Open(filename)
106+
if err != nil {
107+
panic(err)
108+
}
109+
defer f.Close()
110+
h := sha256.New()
111+
_, err = io.Copy(h, f)
112+
if err != nil {
113+
panic(err)
74114
}
75-
checkDocs(t, filepath.Join(distDir, "docs"), expectDocs)
115+
return fmt.Sprintf("%x", h.Sum(nil))
76116
}
77117

78118
func checkDocs(t *testing.T, dir string, expect []string) {

serve/testdata/message.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Test message
2+
with multiple lines and **markdown**

0 commit comments

Comments
 (0)