Skip to content

Commit 4995920

Browse files
chore: add deployment monitoring scripts for metadata and shasum validation
1 parent 14a7289 commit 4995920

12 files changed

Lines changed: 581 additions & 229 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env bash
2+
# Captures version and sha256 metadata for the Homebrew distribution channel (macOS).
3+
# Required env: SNYK_VERSION_DIR, METADATA_SUFFIX
4+
5+
if [[ -n "${CI:-}" ]]; then
6+
set -euo pipefail
7+
else
8+
set -exuo pipefail
9+
fi
10+
11+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12+
HELPERS_DIR="${SCRIPT_DIR}/helpers"
13+
14+
: "${SNYK_VERSION_DIR:?SNYK_VERSION_DIR is required}"
15+
: "${METADATA_SUFFIX:?METADATA_SUFFIX is required}"
16+
17+
VERSION="$(snyk --version | tr -d '\r\n')"
18+
SNYK_PATH="$(command -v snyk)"
19+
SHA256="$(shasum -a 256 "$SNYK_PATH" | awk '{print $1}')"
20+
BINARY_NAME="$(
21+
SNYK_PATH="$SNYK_PATH" bash "${HELPERS_DIR}/resolve-macos-binary-name.sh"
22+
)"
23+
24+
RELEASE_JSON="${GITHUB_WORKSPACE:-$(pwd)}/release.json"
25+
RELEASE_URL="https://static.snyk.io/cli/v${VERSION}/release.json"
26+
curl --retry 2 -sSL "$RELEASE_URL" -o "$RELEASE_JSON"
27+
EXPECTED="$(
28+
BINARY_NAME="$BINARY_NAME" go run -C "${SCRIPT_DIR}" ./cmd/extract-release-json-hash "$RELEASE_JSON"
29+
)"
30+
31+
if [ -z "$EXPECTED" ]; then
32+
echo "Missing expected hash for ${BINARY_NAME} in release.json."
33+
exit 1
34+
fi
35+
36+
if [ "$SHA256" != "$EXPECTED" ]; then
37+
echo "Hash mismatch for ${BINARY_NAME} (homebrew)."
38+
echo "Expected: $EXPECTED"
39+
echo "Actual: $SHA256"
40+
exit 1
41+
fi
42+
43+
SNYK_VERSION_DIR="$SNYK_VERSION_DIR" \
44+
METADATA_SUFFIX="$METADATA_SUFFIX" \
45+
VERSION="$VERSION" \
46+
SHA256="$SHA256" \
47+
CHANNEL="stable" \
48+
BASE_URL="homebrew" \
49+
BINARY_NAME="$BINARY_NAME" \
50+
bash "${HELPERS_DIR}/write-metadata.sh"
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env bash
2+
# Captures version and sha256 metadata for Linux-based distribution channels (npm, snyk-images).
3+
# Required env: SNYK_VERSION_DIR, METADATA_SUFFIX, BASE_URL, MANIFEST_MODE (stable|experimental)
4+
# Optional env: SKIP_HASH_IF_NOT_ELF (set to 1 for npm)
5+
6+
if [[ -n "${CI:-}" ]]; then
7+
set -euo pipefail
8+
else
9+
set -exuo pipefail
10+
fi
11+
12+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13+
HELPERS_DIR="${SCRIPT_DIR}/helpers"
14+
15+
: "${SNYK_VERSION_DIR:?SNYK_VERSION_DIR is required}"
16+
: "${METADATA_SUFFIX:?METADATA_SUFFIX is required}"
17+
: "${BASE_URL:?BASE_URL is required}"
18+
: "${MANIFEST_MODE:?MANIFEST_MODE is required}"
19+
20+
VERSION="$(snyk --version | tr -d '\r\n')"
21+
SNYK_PATH="$(command -v snyk)"
22+
SHA256="$(shasum -a 256 "$SNYK_PATH" | awk '{print $1}')"
23+
BINARY_NAME="$(bash "${HELPERS_DIR}/resolve-linux-binary-name.sh")"
24+
25+
case "$MANIFEST_MODE" in
26+
stable)
27+
MANIFEST_URL="https://downloads.snyk.io/cli/stable/sha256sums.txt.asc"
28+
MISSING_HASH_MSG="Missing expected hash for ${BINARY_NAME} in manifest."
29+
;;
30+
experimental)
31+
MANIFEST_URL="https://downloads.snyk.io/experimental/cli/v${VERSION}/sha256sums.txt.asc"
32+
MISSING_HASH_MSG="Missing expected hash for ${BINARY_NAME} in experimental manifest."
33+
;;
34+
*)
35+
echo "Unknown MANIFEST_MODE: ${MANIFEST_MODE} (expected stable or experimental)"
36+
exit 1
37+
;;
38+
esac
39+
40+
HTTP_STATUS="$(
41+
curl --retry 2 -sSL -w '%{http_code}' -o sha256sums.txt.asc "$MANIFEST_URL"
42+
)"
43+
if [ "$HTTP_STATUS" != "200" ]; then
44+
echo "Failed to download manifest from ${MANIFEST_URL} (HTTP ${HTTP_STATUS})."
45+
exit 1
46+
fi
47+
if [ ! -s sha256sums.txt.asc ]; then
48+
echo "Downloaded manifest from ${MANIFEST_URL} is empty."
49+
exit 1
50+
fi
51+
EXPECTED="$(
52+
MANIFEST_FILE="sha256sums.txt.asc" BINARY_NAME="$BINARY_NAME" \
53+
bash "${HELPERS_DIR}/lookup-hash-from-asc-manifest.sh"
54+
)"
55+
56+
if [ -z "$EXPECTED" ]; then
57+
echo "$MISSING_HASH_MSG"
58+
exit 1
59+
fi
60+
61+
if [ "${SKIP_HASH_IF_NOT_ELF:-}" = "1" ]; then
62+
IS_ELF="$(go run -C "${SCRIPT_DIR}" ./cmd/is-elf-binary "$SNYK_PATH")"
63+
if [ "$IS_ELF" != "1" ]; then
64+
echo "Non-binary CLI detected for npm; skipping CDN hash comparison."
65+
elif [ "$SHA256" != "$EXPECTED" ]; then
66+
echo "Hash mismatch for ${BINARY_NAME} (${BASE_URL})."
67+
echo "Expected: $EXPECTED"
68+
echo "Actual: $SHA256"
69+
exit 1
70+
fi
71+
elif [ "$SHA256" != "$EXPECTED" ]; then
72+
echo "Hash mismatch for ${BINARY_NAME} (${BASE_URL})."
73+
echo "Expected: $EXPECTED"
74+
echo "Actual: $SHA256"
75+
exit 1
76+
fi
77+
78+
SNYK_VERSION_DIR="$SNYK_VERSION_DIR" \
79+
METADATA_SUFFIX="$METADATA_SUFFIX" \
80+
VERSION="$VERSION" \
81+
SHA256="$SHA256" \
82+
CHANNEL="stable" \
83+
BASE_URL="$BASE_URL" \
84+
BINARY_NAME="$BINARY_NAME" \
85+
bash "${HELPERS_DIR}/write-metadata.sh"
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"sort"
9+
"strings"
10+
)
11+
12+
type metadata struct {
13+
Channel string `json:"channel"`
14+
BaseURL string `json:"base_url"`
15+
Binary string `json:"binary"`
16+
SHA256 string `json:"sha256"`
17+
}
18+
19+
type cdnKey struct {
20+
channel string
21+
binary string
22+
}
23+
24+
type cdnEntry struct {
25+
baseURL string
26+
sha256 string
27+
filename string
28+
}
29+
30+
func main() {
31+
snykVersionDir := os.Getenv("SNYK_VERSION_DIR")
32+
if snykVersionDir == "" {
33+
fmt.Fprintln(os.Stderr, "SNYK_VERSION_DIR is required")
34+
os.Exit(1)
35+
}
36+
37+
if !filepath.IsAbs(snykVersionDir) {
38+
workspace := os.Getenv("GITHUB_WORKSPACE")
39+
if workspace == "" {
40+
cwd, err := os.Getwd()
41+
if err != nil {
42+
fmt.Fprintf(os.Stderr, "failed to resolve working directory: %v\n", err)
43+
os.Exit(1)
44+
}
45+
workspace = cwd
46+
}
47+
snykVersionDir = filepath.Join(workspace, snykVersionDir)
48+
}
49+
50+
if err := os.MkdirAll(snykVersionDir, 0o755); err != nil {
51+
fmt.Fprintf(os.Stderr, "failed to create directory: %v\n", err)
52+
os.Exit(1)
53+
}
54+
55+
if err := os.Chdir(snykVersionDir); err != nil {
56+
fmt.Fprintf(os.Stderr, "failed to change directory: %v\n", err)
57+
os.Exit(1)
58+
}
59+
60+
entries, err := os.ReadDir(".")
61+
if err != nil {
62+
fmt.Fprintf(os.Stderr, "failed to read directory: %v\n", err)
63+
os.Exit(1)
64+
}
65+
66+
var metadataFiles []string
67+
for _, entry := range entries {
68+
name := entry.Name()
69+
if strings.HasPrefix(name, "snyk-metadata-") && strings.HasSuffix(name, ".json") {
70+
metadataFiles = append(metadataFiles, name)
71+
}
72+
}
73+
74+
if len(metadataFiles) == 0 {
75+
fmt.Println("No metadata files found; skipping shasum comparison.")
76+
return
77+
}
78+
79+
cdnEntries := make(map[cdnKey][]cdnEntry)
80+
for _, filename := range metadataFiles {
81+
content, err := os.ReadFile(filename)
82+
if err != nil {
83+
fmt.Fprintf(os.Stderr, "failed to read %s: %v\n", filename, err)
84+
os.Exit(1)
85+
}
86+
87+
trimmed := strings.TrimSpace(string(content))
88+
if trimmed == "" {
89+
fmt.Printf("Metadata file %s is empty.\n", filename)
90+
os.Exit(1)
91+
}
92+
93+
var data metadata
94+
if err := json.Unmarshal([]byte(trimmed), &data); err != nil {
95+
snippet := strings.ReplaceAll(trimmed, "\n", "\\n")
96+
if len(snippet) > 200 {
97+
snippet = snippet[:200]
98+
}
99+
fmt.Printf("Invalid JSON in %s: %v\n", filename, err)
100+
fmt.Printf("Content snippet: %s\n", snippet)
101+
os.Exit(1)
102+
}
103+
104+
if data.BaseURL != "static.snyk.io" && data.BaseURL != "downloads.snyk.io" {
105+
continue
106+
}
107+
108+
if data.SHA256 == "" {
109+
continue
110+
}
111+
112+
key := cdnKey{channel: data.Channel, binary: data.Binary}
113+
if key.channel == "" {
114+
key.channel = "unknown"
115+
}
116+
if key.binary == "" {
117+
key.binary = "unknown"
118+
}
119+
120+
cdnEntries[key] = append(cdnEntries[key], cdnEntry{
121+
baseURL: data.BaseURL,
122+
sha256: data.SHA256,
123+
filename: filename,
124+
})
125+
}
126+
127+
if len(cdnEntries) == 0 {
128+
fmt.Println("No CDN metadata entries found; skipping shasum comparison.")
129+
return
130+
}
131+
132+
keys := make([]cdnKey, 0, len(cdnEntries))
133+
for key := range cdnEntries {
134+
keys = append(keys, key)
135+
}
136+
sort.Slice(keys, func(i, j int) bool {
137+
if keys[i].channel != keys[j].channel {
138+
return keys[i].channel < keys[j].channel
139+
}
140+
return keys[i].binary < keys[j].binary
141+
})
142+
143+
failed := false
144+
for _, key := range keys {
145+
entriesForKey := cdnEntries[key]
146+
uniqueSHAs := make(map[string]struct{})
147+
for _, entry := range entriesForKey {
148+
uniqueSHAs[entry.sha256] = struct{}{}
149+
}
150+
151+
if len(uniqueSHAs) > 1 {
152+
failed = true
153+
fmt.Printf("❌ CDN shasum mismatch for %s (%s)\n", key.binary, key.channel)
154+
for _, entry := range entriesForKey {
155+
fmt.Printf(" %s: %s (%s)\n", entry.baseURL, entry.sha256, entry.filename)
156+
}
157+
}
158+
}
159+
160+
if failed {
161+
os.Exit(1)
162+
}
163+
164+
fmt.Println("✅ CDN shasums are consistent within channels.")
165+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"strings"
8+
)
9+
10+
type releaseJSON struct {
11+
Assets map[string]assetEntry `json:"assets"`
12+
}
13+
14+
type assetEntry struct {
15+
SHA256 string `json:"sha256"`
16+
}
17+
18+
func main() {
19+
name := os.Getenv("BINARY_NAME")
20+
if name == "" {
21+
fmt.Fprintln(os.Stderr, "BINARY_NAME environment variable is not set")
22+
os.Exit(1)
23+
}
24+
25+
releasePath := "release.json"
26+
if len(os.Args) > 1 {
27+
releasePath = os.Args[1]
28+
}
29+
30+
content, err := os.ReadFile(releasePath)
31+
if err != nil {
32+
fmt.Fprintf(os.Stderr, "Failed to read release file: %v\n", err)
33+
os.Exit(1)
34+
}
35+
36+
var data releaseJSON
37+
if err := json.Unmarshal(content, &data); err != nil {
38+
fmt.Fprintf(os.Stderr, "Failed to parse release JSON: %v\n", err)
39+
os.Exit(1)
40+
}
41+
42+
asset, ok := data.Assets[name]
43+
if !ok || asset.SHA256 == "" {
44+
fmt.Fprintln(os.Stderr, "Asset not found or SHA256 is empty")
45+
os.Exit(1)
46+
}
47+
48+
hash := strings.Fields(asset.SHA256)[0]
49+
fmt.Println(hash)
50+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"os"
8+
)
9+
10+
const elfMagic = "\x7fELF"
11+
12+
func main() {
13+
if len(os.Args) != 2 {
14+
fmt.Fprintln(os.Stderr, "Usage: is-elf-binary <file>")
15+
os.Exit(1)
16+
}
17+
18+
f, err := os.Open(os.Args[1])
19+
if err != nil {
20+
fmt.Fprintf(os.Stderr, "Failed to read file: %v\n", err)
21+
os.Exit(1)
22+
}
23+
defer f.Close()
24+
25+
header := make([]byte, 4)
26+
_, err = io.ReadFull(f, header)
27+
if err != nil {
28+
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
29+
fmt.Println("0")
30+
return
31+
}
32+
fmt.Fprintf(os.Stderr, "Failed to read file: %v\n", err)
33+
os.Exit(1)
34+
}
35+
36+
if string(header) == elfMagic {
37+
fmt.Println("1")
38+
return
39+
}
40+
41+
fmt.Println("0")
42+
}

0 commit comments

Comments
 (0)