Skip to content

Commit b2d3c8a

Browse files
authored
Docker multi arch (#1)
* Initial commit * Updated main repo readme
1 parent 238f6a7 commit b2d3c8a

File tree

4 files changed

+286
-62
lines changed

4 files changed

+286
-62
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Docker Multi-Arch images overview script
2+
3+
The script will return an overview of your Docker multi-arch images stored in your Cloudsmith repository.
4+
It provides a hierarchial breakdown of each image by tag, showing the index digest and it's associated manifest digests with their platform, cloudsmith sync status and downloads count.
5+
6+
Each image has a total downloads count rolled up from all digests which the current Cloudsmith UI/ API does not provide.
7+
8+
<img src="example.gif" width=50%>
9+
10+
## Prequisities
11+
12+
Configure the Cloudsmith environment variable with your PAT or Service Account Token.
13+
14+
export CLOUDSMITH_API_KEY=<api-key>
15+
16+
17+
## How to use
18+
19+
Execute run.sh and pass in 4 arguements ( domain, org, repo and image name).
20+
21+
./run.sh colinmoynes-test-org docker library/golang
22+
23+
* if not using a custom domain, you can simply pass in an empty string "" as the first param
24+
25+
26+
## So, how does this work?
27+
28+
## Get matching tags
29+
30+
* Fetch all tags via the Docker v2 /tags/list endpoint using the image name e.g. library/nginx
31+
32+
33+
### For each tag
34+
35+
* Pass the tag into manifests/ endpoint and return json for the manifest/list file.
36+
* Read the json and parse out the digests
37+
* Total downloads count value incremented from child manifests
38+
39+
#### For each digest
40+
41+
* Iterate through the digests
42+
* Fetch the platform and os data from the manifest json
43+
* Lookup the digest (version) via the Cloudsmith packages list endpoint using query string.
44+
* Fetch the sync status and downloads count values
45+
* Increment the total downloads value
46+
47+
1.43 MB
Loading

Docker/Multi-Arch-Inspector/run.sh

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Usage: ./run.sh <org> <repo> <img>
5+
# Requires: curl, jq
6+
# Auth: export CLOUDSMITH_API_KEY=<your_token>
7+
8+
# Color setup (auto-disable if not a TTY or tput missing)
9+
if [ -t 1 ] && command -v tput >/dev/null 2>&1 && [ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]; then
10+
GREEN="$(tput setaf 2)"; RED="$(tput setaf 1)"; RESET="$(tput sgr0)"
11+
else
12+
GREEN=""; RED=""; RESET=""
13+
fi
14+
15+
# Icons (fallback to ASCII if not on UTF-8 locale)
16+
CHECK=''; CROSS=''; TIMER=''; VULN='☠️'
17+
case ${LC_ALL:-${LC_CTYPE:-$LANG}} in *UTF-8*|*utf8*) : ;; *) CHECK='OK'; CROSS='X' ;; esac
18+
19+
completed() { printf '%s%s%s %s\n' "$GREEN" "$CHECK" "$RESET" "$*"; }
20+
progress() { printf '%s%s%s %s\n' "$YELLOW" "$TIMER" "$RESET" "$*"; }
21+
quarantined() { printf '%s%s%s %s\n' "$ORANGE" "$VULN" "$RESET" "$*"; }
22+
fail() { printf '%s%s%s %s\n' "$RED" "$CROSS" "$RESET" "$*"; }
23+
24+
CLOUDSMITH_URL="${1:-}"
25+
WORKSPACE="${2:-}"
26+
REPO="${3:-}"
27+
IMG="${4:-}"
28+
29+
if [[ -z "${CLOUDSMITH_URL}" ]]; then
30+
CLOUDSMITH_URL="https://docker.cloudsmith.io"
31+
fi
32+
33+
# uthorization header
34+
AUTH_HEADER=()
35+
if [[ -n "${CLOUDSMITH_API_KEY:-}" ]]; then
36+
AUTH_HEADER=(-H "Authorization: Bearer ${CLOUDSMITH_API_KEY}")
37+
fi
38+
39+
echo
40+
echo "Docker Image: ${WORKSPACE}/${REPO}/${IMG}"
41+
42+
43+
# 1) Get all associated tags from the repo for the image
44+
getDockerTags () {
45+
46+
# 1) Get all applicable tags for the image
47+
echo
48+
TAGS_JSON="$(curl -L -sS "${AUTH_HEADER[@]}" \
49+
-H "Accept: application/vnd.oci.image.manifest.v1+json" \
50+
-H "Cache-Control: no-cache" \
51+
"${CLOUDSMITH_URL}/v2/${WORKSPACE}/${REPO}/${IMG}/tags/list")"
52+
53+
mapfile -t TAGS < <(jq -r '
54+
if type=="object" then
55+
.tags[]
56+
else
57+
.. | objects | .tags? // empty
58+
end
59+
' <<< "${TAGS_JSON}" | awk 'NF' | sort -u)
60+
61+
if (( ${#TAGS[@]} == 0 )); then
62+
echo "No tags found for the image."
63+
exit 1
64+
fi
65+
66+
nTAGS="${TAGS[@]}"
67+
68+
}
69+
70+
getDigestData () {
71+
72+
local digest="$1"
73+
mapfile -t ARCHS < <(jq -r --arg d "${digest}" '
74+
if type=="object" and (.manifests? // empty) then
75+
.manifests[]?
76+
| select(.digest == $d )
77+
| ((.platform.os // "") + "/" + (.platform.architecture // ""))
78+
else
79+
.. | objects | .architecture? // empty
80+
end
81+
' <<< "${MANIFEST_JSON}" | awk 'NF' | sort -u)
82+
83+
if (( ${#ARCHS[@]} == 0 )); then
84+
echo "No architecture data found."
85+
exit 1
86+
fi
87+
88+
# Get the package data from Cloudsmith API packages list endpoint
89+
getPackageData () {
90+
91+
#echo "Fetching data for the images."
92+
local digest="$1"
93+
local version="${digest#*:}" # Strip sha256: from string
94+
95+
# Get package data using the query string "version:<digest>"
96+
PKG_DETAILS="$(curl -sS "${AUTH_HEADER[@]}" \
97+
-H "Cache-Control: no-cache" \
98+
--get "${API_BASE}?query=version:${version}")"
99+
100+
mapfile -t STATUS < <(jq -r '
101+
.. | objects | .status_str
102+
' <<< "${PKG_DETAILS}" | awk 'NF' | sort -u)
103+
104+
mapfile -t DOWNLOADS < <(jq -r '
105+
.. | objects | .downloads
106+
' <<< "${PKG_DETAILS}" | awk 'NF' | sort -u)
107+
108+
109+
# handle the different status's
110+
case "${STATUS[0]}" in
111+
Completed)
112+
echo " |____ Status: ${STATUS[0]} ${CHECK}"
113+
;;
114+
115+
"In Progress")
116+
echo " |____ Status: ${STATUS[0]} ${TIMER}"
117+
;;
118+
119+
Quarantined)
120+
echo " |____ Status: ${STATUS[1]} ${VULN}"
121+
;;
122+
123+
Failed)
124+
echo " |____ Status: ${STATUS[0]} ${FAIL}"
125+
;;
126+
127+
esac
128+
129+
case "${STATUS[1]}" in
130+
Completed)
131+
echo " |____ Status: ${STATUS[1]} ${CHECK}"
132+
;;
133+
134+
"In Progress")
135+
echo " |____ Status: ${STATUS[1]} ${TIMER}"
136+
;;
137+
138+
Quarantined)
139+
echo " |____ Status: ${STATUS[1]} ${VULN}"
140+
;;
141+
142+
Failed)
143+
echo " |____ Status: ${STATUS[1]} ${FAIL}"
144+
;;
145+
146+
esac
147+
148+
if (( ${#DOWNLOADS[@]} == 3 )); then
149+
echo " |____ Downloads: ${DOWNLOADS[1]}"
150+
count=${DOWNLOADS[1]}
151+
totalDownloads=$((totalDownloads+count))
152+
else
153+
echo " |____ Downloads: ${DOWNLOADS[0]}"
154+
fi
155+
156+
}
157+
158+
echo " - ${digest}"
159+
echo " - Platform: ${ARCHS}"
160+
getPackageData "${digest}"
161+
162+
}
163+
164+
165+
# Get the individual digests for the tag
166+
getDockerDigests () {
167+
168+
local nTAG="$1"
169+
local totalDownloads=0
170+
API_BASE="https://api.cloudsmith.io/v1/packages/${WORKSPACE}/${REPO}/"
171+
172+
index_digest="$(curl -fsSL "${AUTH_HEADER[@]}" \
173+
-H "Accept: application/vnd.oci.image.manifest.v1+json" \
174+
-o /dev/null \
175+
-w "%header{Docker-Content-Digest}" \
176+
"${CLOUDSMITH_URL}/v2/${WORKSPACE}/${REPO}/${IMG}/manifests/${nTAG}")"
177+
178+
echo
179+
echo "🐳 ${WORKSPACE}/${REPO}/${IMG}:${nTAG}"
180+
echo " Index Digest: ${index_digest}"
181+
182+
183+
MANIFEST_JSON="$(curl -L -sS "${AUTH_HEADER[@]}" \
184+
-H "Accept: application/vnd.oci.image.manifest.v1+json" \
185+
-H "Cache-Control: no-cache" \
186+
"${CLOUDSMITH_URL}/v2/${WORKSPACE}/${REPO}/${IMG}/manifests/${nTAG}")"
187+
188+
189+
# Parse out digest(s) and architectures
190+
# - Prefer `.manifests[].digest` (typical manifest list)
191+
# - Fallback to any `.digest` fields if needed, then unique
192+
mapfile -t DIGESTS < <(jq -r '
193+
if type=="object" and (.manifests? // empty and (.manifests[].platform.architecture )) then
194+
.manifests[]?
195+
| select((.platform.architecture? // "unknown") | ascii_downcase != "unknown")
196+
| .digest
197+
else
198+
.. | objects | .digest? // empty
199+
end
200+
' <<< "${MANIFEST_JSON}" | awk 'NF' | sort -u)
201+
202+
if (( ${#DIGESTS[@]} == 0 )); then
203+
echo "No digests found."
204+
exit 1
205+
fi
206+
207+
for i in "${!DIGESTS[@]}"; do
208+
echo
209+
getDigestData "${DIGESTS[i]}"
210+
echo
211+
done
212+
echo " |___ Total Downloads: ${totalDownloads}"
213+
214+
}
215+
216+
217+
# Lookup Docker multi-arch images and output an overview
218+
getDockerTags
219+
read -r -a images <<< "$nTAGS"
220+
echo "Found matching tags:"
221+
echo
222+
for t in "${!images[@]}"; do
223+
tag=" - ${images[t]}"
224+
echo "$tag"
225+
done
226+
227+
echo
228+
for t in "${!images[@]}"; do
229+
getDockerDigests "${images[t]}"
230+
done
231+
232+
233+
234+
235+
236+
237+

README.md

Lines changed: 2 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,6 @@
1-
# 🚀 Cloudsmith CENG Template
1+
# 🚀 Cloudsmith Support Engineering
22

3-
A reusable template repository maintained by the **Customer Engineering (CENG)** team at Cloudsmith.
4-
This repo is intended to accelerate the development of examples, scripts, integrations, and demo workflows that help customers use Cloudsmith more effectively.
5-
6-
---
7-
8-
## 📦 What’s Inside
9-
10-
- GitHub Issue Forms for bugs and feature requests
11-
- CI/CD example workflow (Python-based)
12-
- Contribution and pull request templates
13-
- Environment variable and code linting examples
14-
- Directory structure for `src/` and `tests/`
15-
16-
---
17-
18-
## 📁 Structure
19-
20-
```
21-
.
22-
├── .github/ # GitHub-specific automation and templates
23-
│ ├── ISSUE_TEMPLATE/ # Issue forms using GitHub Issue Forms
24-
│ │ ├── bug_report.yml # Form for reporting bugs
25-
│ │ └── feature_request.yml # Form for suggesting features
26-
│ ├── workflows/ # GitHub Actions workflows (e.g., CI pipelines)
27-
│ ├── PULL_REQUEST_TEMPLATE.md # Template used when creating pull requests
28-
│ └── CODEOWNERS # Defines reviewers for specific paths
29-
├── src/ # Scripts, API integrations, or example tools
30-
├── tests/ # Tests for scripts and tools in src/
31-
├── .env.example # Sample environment config (e.g., API keys)
32-
├── .gitignore # Ignore rules for Git-tracked files
33-
├── .editorconfig # Code style config to ensure consistency across IDEs
34-
├── CHANGELOG.md # Log of project changes and version history
35-
├── CONTRIBUTING.md # Guidelines and checklists for contributors
36-
├── LICENSE # Licensing information (Apache 2.0)
37-
└── README.md # This file
38-
```
39-
40-
---
41-
42-
## 🛠 Getting Started
43-
44-
1. Clone the template:
45-
```bash
46-
git clone https://github.com/cloudsmith-examples/ceng-template.git
47-
cd ceng-template
48-
```
49-
50-
2. Install any dependencies or activate your environment.
51-
52-
3. Start building your example in the `src/` directory.
53-
54-
4. Use the `.env.example` as a guide for credentials if needed.
55-
56-
---
57-
58-
## 🧩 Use Cases
59-
60-
- Building and testing Cloudsmith integrations for CI/CD platforms
61-
- Creating reproducible customer issue examples
62-
- Building Cloudsmith CLI or API automations
63-
- Prototyping workflows for CI/CD platforms
3+
A collection of useful resources for assisting with various components of Cloudsmith.
644

655
---
666

0 commit comments

Comments
 (0)