A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
atcr.io
docker
container
atproto
go
1#!/bin/bash
2#
3# ATCR End-to-End Test Script
4# Tests single-arch and multi-arch image push/pull flows
5#
6# Usage:
7# ./test-e2e.sh # Run full test suite
8# ./test-e2e.sh --multi # Skip to multi-arch test only
9# REGISTRY=localhost:5000 ./test-e2e.sh # Custom registry
10# NAMESPACE=myuser ./test-e2e.sh # Custom namespace
11# VERBOSE=0 ./test-e2e.sh # Hide docker output
12#
13# To see bash command execution, edit this file and uncomment 'set -x' below
14#
15
16set -e
17
18# Verbose mode - shows docker command output
19# Set VERBOSE=0 to hide docker output: VERBOSE=0 ./test-e2e.sh
20VERBOSE="${VERBOSE:-1}"
21
22# Bash trace mode - shows every bash command as it executes
23# Uncomment the line below to see bash commands as they execute
24# set -x
25
26# Colors for output
27RED='\033[0;31m'
28GREEN='\033[0;32m'
29YELLOW='\033[1;33m'
30BLUE='\033[0;34m'
31NC='\033[0m' # No Color
32
33# Configuration
34REGISTRY="${REGISTRY:-127.0.0.1:5000}"
35NAMESPACE="${NAMESPACE:-evan.jarrett.net}"
36IMAGE_NAME="test-image"
37TAG="latest"
38MULTI_ARCH_TAG="multiarch"
39
40# Full image references
41SINGLE_ARCH_IMAGE="${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${TAG}"
42MULTI_ARCH_IMAGE="${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${MULTI_ARCH_TAG}"
43AMD64_IMAGE="${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${TAG}-amd64"
44ARM64_IMAGE="${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${TAG}-arm64"
45
46# Temporary directory for test images
47BUILD_DIR=$(mktemp -d)
48
49# Cleanup function
50cleanup() {
51 log_info "Cleaning up..."
52 rm -rf ${BUILD_DIR}
53
54 # Clean up any dangling manifest lists
55 docker manifest rm ${MULTI_ARCH_IMAGE} 2>/dev/null || true
56}
57
58trap cleanup EXIT
59
60# Logging functions
61log_info() {
62 echo -e "${GREEN}[INFO]${NC} $1"
63}
64
65log_error() {
66 echo -e "${RED}[ERROR]${NC} $1"
67}
68
69log_warn() {
70 echo -e "${YELLOW}[WARN]${NC} $1"
71}
72
73log_step() {
74 echo -e "\n${GREEN}==>${NC} $1"
75}
76
77log_cmd() {
78 echo -e "${BLUE}[CMD]${NC} $*"
79 if [ "$VERBOSE" = "1" ]; then
80 "$@"
81 else
82 "$@" > /dev/null 2>&1
83 fi
84}
85
86# Check if docker compose services are running
87check_compose_services() {
88 log_step "Checking docker compose services..."
89
90 if ! docker compose ps | grep -q "Up"; then
91 log_warn "Some services may not be running. Starting services..."
92 docker compose up -d
93 sleep 5
94 fi
95
96 # Check for errors in logs
97 log_info "Checking for errors in service logs..."
98 if docker compose logs --tail=50 | grep -i "error" | grep -v "level=error msg=\"error processing" | grep -v "test"; then
99 log_warn "Found some errors in logs (may be normal)"
100 else
101 log_info "No critical errors found in recent logs"
102 fi
103}
104
105# Build a simple test image
106build_single_arch_image() {
107 log_step "Building single-arch test image..."
108
109 cat > ${BUILD_DIR}/Dockerfile <<EOF
110FROM alpine:latest
111
112# Add some content to make the image non-trivial
113RUN apk add --no-cache curl bash
114
115# Create a test file with some content
116RUN echo "This is a test image created at \$(date)" > /test.txt
117RUN echo "Architecture: \$(uname -m)" >> /test.txt
118
119# Add a simple script
120RUN echo '#!/bin/sh' > /test.sh && \\
121 echo 'echo "Test image running successfully!"' >> /test.sh && \\
122 echo 'cat /test.txt' >> /test.sh && \\
123 chmod +x /test.sh
124
125CMD ["/test.sh"]
126EOF
127
128 log_cmd docker build -t ${SINGLE_ARCH_IMAGE} ${BUILD_DIR}
129 log_info "Built single-arch image: ${SINGLE_ARCH_IMAGE}"
130}
131
132# Build multi-arch images using docker manifest create (old school!)
133build_multi_arch_images() {
134 log_step "Building multi-arch images (amd64 and arm64) using docker manifest..."
135
136 # Create Dockerfile for multi-arch builds
137 cat > ${BUILD_DIR}/Dockerfile.multiarch <<EOF
138FROM alpine:latest
139
140ARG TARGETARCH=unknown
141ARG TARGETOS=linux
142
143# Add some content to make the image non-trivial
144RUN apk add --no-cache curl bash
145
146# Create a test file with arch info
147RUN echo "This is a multi-arch test image" > /test.txt
148RUN echo "Target OS: \${TARGETOS}" >> /test.txt
149RUN echo "Target Architecture: \${TARGETARCH}" >> /test.txt
150RUN echo "Built at: \$(date)" >> /test.txt
151
152# Add a simple script
153RUN echo '#!/bin/sh' > /test.sh && \\
154 echo 'echo "Multi-arch test image running!"' >> /test.sh && \\
155 echo 'cat /test.txt' >> /test.sh && \\
156 chmod +x /test.sh
157
158CMD ["/test.sh"]
159EOF
160
161 # Build amd64 image
162 log_info "Building amd64 image..."
163 log_cmd docker build \
164 --platform linux/amd64 \
165 --build-arg TARGETARCH=amd64 \
166 --build-arg TARGETOS=linux \
167 -t ${AMD64_IMAGE} \
168 -f ${BUILD_DIR}/Dockerfile.multiarch \
169 ${BUILD_DIR}
170
171 # Push amd64 image
172 log_info "Pushing amd64 image..."
173 echo -e "${BLUE}[CMD]${NC} docker push ${AMD64_IMAGE}"
174 if ! docker push ${AMD64_IMAGE}; then
175 log_error "Failed to push amd64 image"
176 return 1
177 fi
178
179 # Build arm64 image
180 log_info "Building arm64 image..."
181 log_cmd docker build \
182 --platform linux/arm64 \
183 --build-arg TARGETARCH=arm64 \
184 --build-arg TARGETOS=linux \
185 -t ${ARM64_IMAGE} \
186 -f ${BUILD_DIR}/Dockerfile.multiarch \
187 ${BUILD_DIR}
188
189 # Push arm64 image
190 log_info "Pushing arm64 image..."
191 echo -e "${BLUE}[CMD]${NC} docker push ${ARM64_IMAGE}"
192 if ! docker push ${ARM64_IMAGE}; then
193 log_error "Failed to push arm64 image"
194 return 1
195 fi
196
197 # Create manifest list
198 log_info "Creating multi-arch manifest list..."
199 echo -e "${BLUE}[CMD]${NC} docker manifest create --insecure ${MULTI_ARCH_IMAGE} ${AMD64_IMAGE} ${ARM64_IMAGE}"
200 docker manifest create --insecure ${MULTI_ARCH_IMAGE} \
201 ${AMD64_IMAGE} \
202 ${ARM64_IMAGE}
203
204 # Annotate manifests with platform info
205 log_info "Annotating manifest with platform info..."
206 docker manifest annotate ${MULTI_ARCH_IMAGE} ${AMD64_IMAGE} \
207 --os linux --arch amd64
208 docker manifest annotate ${MULTI_ARCH_IMAGE} ${ARM64_IMAGE} \
209 --os linux --arch arm64
210
211 # Push the manifest list
212 log_info "Pushing multi-arch manifest list..."
213 echo -e "${BLUE}[CMD]${NC} docker manifest push --insecure ${MULTI_ARCH_IMAGE}"
214 docker manifest push --insecure ${MULTI_ARCH_IMAGE}
215
216 log_info "Multi-arch manifest created and pushed: ${MULTI_ARCH_IMAGE}"
217}
218
219# Push single-arch image and verify digest
220push_single_arch_image() {
221 log_step "Pushing single-arch image..."
222
223 echo -e "${BLUE}[CMD]${NC} docker push ${SINGLE_ARCH_IMAGE}"
224 local push_output
225 push_output=$(docker push ${SINGLE_ARCH_IMAGE} 2>&1 | tee /dev/tty)
226
227 # Extract and verify digest
228 local digest
229 digest=$(echo "$push_output" | grep -oP 'digest: \K[a-z0-9:]+' | tail -1)
230
231 if [ -z "$digest" ]; then
232 log_error "Failed to get digest from push output"
233 return 1
234 fi
235
236 log_info "Pushed with digest: ${digest}"
237
238 # Verify we can reference by digest
239 log_info "Verifying digest reference..."
240 echo -e "${BLUE}[CMD]${NC} docker manifest inspect --insecure ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}@${digest}"
241 docker manifest inspect --insecure "${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}@${digest}"
242 log_info "Digest verification successful!"
243}
244
245# Verify multi-arch manifest
246verify_multi_arch_manifest() {
247 log_step "Verifying multi-arch manifest..."
248
249 echo -e "${BLUE}[CMD]${NC} docker manifest inspect --insecure ${MULTI_ARCH_IMAGE}"
250 local manifest
251 manifest=$(docker manifest inspect --insecure ${MULTI_ARCH_IMAGE} | tee /dev/tty)
252
253 # Check for both architectures (check for "amd64" and "arm64" in platform.architecture)
254 if echo "$manifest" | grep -q '"architecture": "amd64"'; then
255 log_info "Found linux/amd64 manifest"
256 else
257 log_error "Missing linux/amd64 manifest"
258 return 1
259 fi
260
261 if echo "$manifest" | grep -q '"architecture": "arm64"'; then
262 log_info "Found linux/arm64 manifest"
263 else
264 log_error "Missing linux/arm64 manifest"
265 return 1
266 fi
267
268 # Extract digest
269 local digest
270 digest=$(echo "$manifest" | jq -r '.manifests[0].digest' 2>/dev/null || echo "$manifest" | grep -oP 'sha256:[a-f0-9]+' | head -1)
271
272 if [ -n "$digest" ]; then
273 log_info "Multi-arch manifest digest: ${digest}"
274 fi
275}
276
277# Remove local images
278cleanup_local_images() {
279 log_step "Removing local images..."
280
281 echo -e "${BLUE}[CMD]${NC} docker rmi ${SINGLE_ARCH_IMAGE}"
282 docker rmi ${SINGLE_ARCH_IMAGE} || true
283
284 echo -e "${BLUE}[CMD]${NC} docker rmi ${AMD64_IMAGE}"
285 docker rmi ${AMD64_IMAGE} || true
286
287 echo -e "${BLUE}[CMD]${NC} docker rmi ${ARM64_IMAGE}"
288 docker rmi ${ARM64_IMAGE} || true
289
290 echo -e "${BLUE}[CMD]${NC} docker rmi ${MULTI_ARCH_IMAGE}"
291 docker rmi ${MULTI_ARCH_IMAGE} || true
292
293 # Clean up manifest list
294 docker manifest rm ${MULTI_ARCH_IMAGE} 2>/dev/null || true
295
296 # Also remove any cached layers
297 log_info "Pruning dangling images..."
298 log_cmd docker image prune -f
299
300 log_info "Local images removed"
301}
302
303# Pull images back
304pull_images() {
305 log_step "Pulling images back from registry..."
306
307 # Pull single-arch image
308 log_info "Pulling single-arch image..."
309 echo -e "${BLUE}[CMD]${NC} docker pull ${SINGLE_ARCH_IMAGE}"
310 if docker pull ${SINGLE_ARCH_IMAGE}; then
311 log_info "Successfully pulled: ${SINGLE_ARCH_IMAGE}"
312 else
313 log_error "Failed to pull single-arch image"
314 return 1
315 fi
316
317 # Pull multi-arch image
318 log_info "Pulling multi-arch image..."
319 echo -e "${BLUE}[CMD]${NC} docker pull ${MULTI_ARCH_IMAGE}"
320 if docker pull ${MULTI_ARCH_IMAGE}; then
321 log_info "Successfully pulled: ${MULTI_ARCH_IMAGE}"
322 else
323 log_error "Failed to pull multi-arch image"
324 return 1
325 fi
326}
327
328# Test running the images
329test_images() {
330 log_step "Testing pulled images..."
331
332 # Test single-arch image
333 log_info "Running single-arch image..."
334 log_cmd docker run --rm ${SINGLE_ARCH_IMAGE}
335
336 # Test multi-arch image
337 log_info "Running multi-arch image..."
338 log_cmd docker run --rm ${MULTI_ARCH_IMAGE}
339
340 log_info "All images ran successfully!"
341}
342
343# Check for errors in compose logs after operations
344check_compose_logs() {
345 log_step "Checking compose logs for errors..."
346
347 local error_count
348 error_count=$(docker compose logs --tail=100 | grep -i "error" | grep -v "level=error msg=\"error processing" | grep -v "test" | wc -l)
349
350 if [ "$error_count" -gt 0 ]; then
351 log_warn "Found ${error_count} error messages in logs:"
352 docker compose logs --tail=100 | grep -i "error" | grep -v "level=error msg=\"error processing" | grep -v "test" | tail -10
353 else
354 log_info "No errors found in compose logs"
355 fi
356}
357
358# Multi-arch only test flow
359multi_arch_only() {
360 log_step "Starting ATCR multi-arch test (skipping single-arch)"
361 log_info "Registry: ${REGISTRY}"
362 log_info "Namespace: ${NAMESPACE}"
363 log_info "Build directory: ${BUILD_DIR}"
364 log_info "Verbose mode: ${VERBOSE} (set VERBOSE=0 to hide docker output)"
365
366 # Check prerequisites
367 if ! command -v docker &> /dev/null; then
368 log_error "Docker is not installed"
369 exit 1
370 fi
371
372 if ! command -v jq &> /dev/null; then
373 log_warn "jq is not installed - some checks may be limited"
374 fi
375
376 # Run multi-arch test steps only
377 check_compose_services
378 build_multi_arch_images
379 verify_multi_arch_manifest
380 cleanup_local_images
381 pull_images
382 log_info "Running multi-arch image..."
383 log_cmd docker run --rm ${MULTI_ARCH_IMAGE}
384 check_compose_logs
385
386 log_step "Multi-arch test completed successfully!"
387 echo -e "${GREEN}========================================${NC}"
388 echo -e "${GREEN}Multi-arch test passed!${NC}"
389 echo -e "${GREEN}========================================${NC}"
390}
391
392# Main test flow
393main() {
394 log_step "Starting ATCR end-to-end test"
395 log_info "Registry: ${REGISTRY}"
396 log_info "Namespace: ${NAMESPACE}"
397 log_info "Build directory: ${BUILD_DIR}"
398 log_info "Verbose mode: ${VERBOSE} (set VERBOSE=0 to hide docker output)"
399
400 # Check prerequisites
401 if ! command -v docker &> /dev/null; then
402 log_error "Docker is not installed"
403 exit 1
404 fi
405
406 if ! command -v jq &> /dev/null; then
407 log_warn "jq is not installed - some checks may be limited"
408 fi
409
410 # Run test steps
411 check_compose_services
412 build_single_arch_image
413 push_single_arch_image
414 build_multi_arch_images
415 verify_multi_arch_manifest
416 cleanup_local_images
417 pull_images
418 test_images
419 check_compose_logs
420
421 log_step "End-to-end test completed successfully!"
422 echo -e "${GREEN}========================================${NC}"
423 echo -e "${GREEN}All tests passed!${NC}"
424 echo -e "${GREEN}========================================${NC}"
425}
426
427# Parse arguments
428if [ "$1" = "--multi" ]; then
429 multi_arch_only
430else
431 main "$@"
432fi