Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion test/e2e/framework/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ func podManifest(podTestLabel string) (*v1.Pod, error) {
pod.Spec.Containers[0].Name = "registry"
pod.Spec.Containers[0].Image = imageutils.GetE2EImage(imageutils.Agnhost)
pod.Spec.Containers[0].ImagePullPolicy = v1.PullIfNotPresent
pod.Spec.Containers[0].Args = []string{"fake-registry-server", "--private"}
pod.Spec.Containers[0].Command = []string{"powershell", "-Command"}
pod.Spec.Containers[0].Args = []string{"Copy-Item c:\\hpc\\agnhost -Destination c:\\hpc\\agnhost.exe; c:\\hpc\\agnhost.exe fake-registry-server --private"}
pod.Spec.Containers[0].Ports = []v1.ContainerPort{
{
Name: "http",
Expand All @@ -127,6 +128,10 @@ func podManifest(podTestLabel string) (*v1.Pod, error) {
},
}
pod.Spec.Containers[0].SecurityContext.RunAsUser = ptr.To[int64](5123)
pod.Spec.Containers[0].SecurityContext.WindowsOptions = &v1.WindowsSecurityContextOptions{
RunAsUserName: ptr.To("NT AUTHORITY\\SYSTEM"),
HostProcess: ptr.To(true),
}
// setting HostNetwork to true so that the registry is accessible on localhost:<hostport>
// and we don't have to deal with any CNI quirks.
pod.Spec.HostNetwork = true
Expand Down
8 changes: 1 addition & 7 deletions test/images/agnhost/BASEIMAGE
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
linux/amd64=alpine:3.21
linux/arm=arm32v6/alpine:3.21
linux/arm64=arm64v8/alpine:3.21
linux/ppc64le=ppc64le/alpine:3.21
linux/s390x=s390x/alpine:3.21
windows/amd64/1809=REGISTRY/busybox:1.29-2-windows-amd64-1809
windows/amd64/ltsc2022=REGISTRY/busybox:1.29-2-windows-amd64-ltsc2022
windows/amd64/ltsc2022=REGISTRY/busybox:1.37.1-2-windows-amd64-ltsc2022
23 changes: 23 additions & 0 deletions test/images/agnhost/Dockerfile_windows
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@
ARG BASEIMAGE
ARG REGISTRY
ARG OS_VERSION
ARG GOLANG_VERSION

FROM --platform=linux/amd64 golang:$GOLANG_VERSION AS prepregistry

RUN go install github.com/google/go-containerregistry/cmd/crane@latest && \
apt-get update && apt-get install -y jq

COPY fakeregistryserver/prepare_registry.sh /prepare_registry.sh
COPY fakeregistryserver/images_windows.txt /images.txt

RUN chmod +x /prepare_registry.sh

# run the script during the build to create the artifact inside the image
RUN /prepare_registry.sh

# Replace colons with underscores in filenames ONLY for Windows compatibility
# Do NOT modify file contents - they must remain valid OCI format
RUN find /registry -type f -name '*:*' | while read -r file; do \
newname=$(echo "$file" | sed 's/sha256:/sha256_/g'); \
mv "$file" "$newname"; \
done

# We're using a Linux image to unpack the archives, then we're copying them over to Windows.
FROM --platform=linux/amd64 alpine:3.21 as prep
Expand Down Expand Up @@ -48,6 +69,8 @@ COPY --from=servercore-helper /Windows/System32/netapi32.dll /Windows/System32/n
COPY --from=prep /coredns.exe /coredns.exe
COPY --from=prep /iperf /iperf
COPY --from=prep /wincoreutils/sync.exe /bin/sync.exe
# Copy the fake registry data from the preparer stage
COPY --from=prepregistry /registry /var/registry

# NOTE(claudiub): docker buildx sets the PATH env variable to a Linux-like PATH, which is not desirable.
ENV PATH="C:\dig\;C:\bin\;C:\curl\;C:\Windows\system32;C:\Windows;C:\Program Files\PowerShell;"
Expand Down
2 changes: 1 addition & 1 deletion test/images/agnhost/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.59
2.62
1 change: 1 addition & 0 deletions test/images/agnhost/fakeregistryserver/images_windows.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pause 3.10.1 testing windows
11 changes: 7 additions & 4 deletions test/images/agnhost/fakeregistryserver/prepare_registry.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ prepare_image() {
local image_name="$1"
local tag="$2"
local internal_tag="$3"
local platform="$4"
local image_dir="$REGISTRY_DIR/$image_name"

echo "--- Preparing image: ${image_name}:${tag} as ${image_name}:${internal_tag} ---"
Expand All @@ -38,8 +39,8 @@ prepare_image() {

echo "Downloading and filtering manifest list for $image_name:$tag..."
local tmp_manifest_path="$image_dir/manifests/tmp_${internal_tag}"
# download the manifest and pipe it to jq to filter out windows images
crane manifest "$REGISTRY_URL/$image_name:$tag" | jq '.manifests |= map(select(.platform.os != "windows"))' > "$tmp_manifest_path"
# download the manifest and pipe it to jq to filter based on the platform
crane manifest "$REGISTRY_URL/$image_name:$tag" | jq --arg platform "$platform" '.manifests |= map(select(.platform.os == $platform))' > "$tmp_manifest_path"
echo "Saved manifest list to $tmp_manifest_path"

local manifest_digest
Expand Down Expand Up @@ -79,10 +80,12 @@ prepare_image() {
mkdir -p "$REGISTRY_DIR"

echo "--> Processing images.txt..."
while read -r image tag internal_tag; do
while read -r image tag internal_tag platform; do
# skip empty lines or comments
[[ -z "$image" || "$image" == \#* ]] && continue
prepare_image "$image" "$tag" "$internal_tag"
# default platform to 'linux' if not specified
platform="${platform:-linux}"
prepare_image "$image" "$tag" "$internal_tag" "$platform"
done < /images.txt

echo "--> Done"
48 changes: 45 additions & 3 deletions test/images/agnhost/fakeregistryserver/registryserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"log"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -52,6 +54,26 @@ var CmdFakeRegistryServer = &cobra.Command{
}

func main(cmd *cobra.Command, args []string) {
// Set registryDir relative to the executable location if not explicitly set
if registryDir == "/var/registry" {
if exePath, err := os.Executable(); err == nil {
exeDir := filepath.Dir(exePath)
registryDir = filepath.Join(exeDir, "var", "registry")
}
}

log.Printf("Registry server starting with registry directory: %s", registryDir)

// Check if directory exists and list contents
if entries, err := os.ReadDir(registryDir); err != nil {
log.Printf("WARNING: Cannot read registry directory %s: %v", registryDir, err)
} else {
log.Printf("Found %d entries in registry directory:", len(entries))
for _, entry := range entries {
log.Printf(" - %s (isDir: %v)", entry.Name(), entry.IsDir())
}
}

registryMux := NewRegistryServerMux(private)

addr := fmt.Sprintf(":%d", port)
Expand Down Expand Up @@ -88,7 +110,9 @@ func auth(h http.Handler) http.Handler {

// handleBlobs serves blob requests
func handleBlobs(w http.ResponseWriter, r *http.Request, imageName, identifier string) {
filePath := fmt.Sprintf("%s/%s/blobs/%s", registryDir, imageName, identifier)
// On Windows, filenames use underscores instead of colons
fileIdentifier := toFilename(identifier)
filePath := fmt.Sprintf("%s/%s/blobs/%s", registryDir, imageName, fileIdentifier)
w.Header().Set("Content-Type", "application/octet-stream")
log.Printf("Serving blob: %s", filePath)
http.ServeFile(w, r, filePath)
Expand All @@ -98,23 +122,32 @@ func handleBlobs(w http.ResponseWriter, r *http.Request, imageName, identifier s
// based on the manifest's mediaType field. If the identifier is a tag, it
// reads the digest from the tag file and issues a redirect.
func handleManifests(w http.ResponseWriter, r *http.Request, imageName, identifier string) {
filePath := fmt.Sprintf("%s/%s/manifests/%s", registryDir, imageName, identifier)
// On Windows, filenames use underscores instead of colons
fileIdentifier := toFilename(identifier)
filePath := fmt.Sprintf("%s/%s/manifests/%s", registryDir, imageName, fileIdentifier)

// if the identifier is not a digest, assume it's a tag and perform a redirect.
if !strings.HasPrefix(identifier, "sha256:") {
log.Printf("Looking for tag file: %s", filePath)
digest, err := os.ReadFile(filePath)
if err != nil {
log.Printf("Tag file not found: %s, error: %v", filePath, err)
http.NotFound(w, r)
return
}
redirectURL := strings.Replace(r.URL.String(), identifier, strings.TrimSpace(string(digest)), 1)
digestStr := strings.TrimSpace(string(digest))
log.Printf("Tag file contains digest: %s", digestStr)
redirectURL := strings.Replace(r.URL.String(), identifier, digestStr, 1)
log.Printf("Redirecting from %s to %s", r.URL.String(), redirectURL)
w.Header().Set("Location", redirectURL)
w.WriteHeader(http.StatusTemporaryRedirect)
return
}

log.Printf("Serving manifest: %s", filePath)
manifestContent, err := os.ReadFile(filePath)
if err != nil {
log.Printf("Manifest file not found: %s, error: %v", filePath, err)
http.NotFound(w, r)
return
}
Expand Down Expand Up @@ -159,3 +192,12 @@ func handleV2(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
}
}

// toFilename converts an identifier to a valid filename for the current OS.
// On Windows, colons in sha256 digests are replaced with underscores.
func toFilename(identifier string) string {
if runtime.GOOS == "windows" {
return strings.ReplaceAll(identifier, ":", "_")
}
return identifier
}
2 changes: 1 addition & 1 deletion test/images/image-util.sh
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ bin() {
golang:"${GOLANG_VERSION}" \
/bin/bash -c "\
cd /go/src/k8s.io/kubernetes/test/images/${SRC_DIR} && \
CGO_ENABLED=0 ${arch_prefix} GOOS=${OS} GOARCH=${ARCH} go build -a -installsuffix cgo --ldflags \"-w ${LD_FLAGS:-}\" -o ${TARGET}/${SRC} ./$(dirname "${SRC}")"
CGO_ENABLED=0 ${arch_prefix} GOOS=${OS} GOARCH=${ARCH} go build -buildvcs=false -a -installsuffix cgo --ldflags \"-w ${LD_FLAGS:-}\" -o ${TARGET}/${SRC} ./$(dirname "${SRC}")"
done
}

Expand Down
2 changes: 1 addition & 1 deletion test/utils/image/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ const (
func initImageConfigs(list RegistryList) (map[ImageID]Config, map[ImageID]Config) {
configs := map[ImageID]Config{}
configs[AgnhostPrev] = Config{list.PromoterE2eRegistry, "agnhost", "2.55"}
configs[Agnhost] = Config{list.PromoterE2eRegistry, "agnhost", "2.59"}
configs[Agnhost] = Config{"wcctpublic.azurecr.io/e2e-test-images", "agnhost", "2.62"}
configs[AgnhostPrivate] = Config{list.PrivateRegistry, "agnhost", "2.6"}
configs[APIServer] = Config{list.PromoterE2eRegistry, "sample-apiserver", "1.29.2"}
configs[AppArmorLoader] = Config{list.PromoterE2eRegistry, "apparmor-loader", "1.4"}
Expand Down